@pylonsync/create-pylon 0.19.1 → 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (696) hide show
  1. package/bin/create-pylon.js +30 -7
  2. package/package.json +1 -1
  3. package/templates/_root/AGENTS.md +1 -1
  4. package/templates/agency/.env.example +7 -0
  5. package/templates/agency/AGENTS.md +1 -1
  6. package/templates/agency/README.md +28 -3
  7. package/templates/agency/app/(marketing)/contact-form.tsx +20 -18
  8. package/templates/agency/app/(marketing)/page.tsx +3 -3
  9. package/templates/agency/app/(marketing)/work/page.tsx +3 -3
  10. package/templates/agency/app/auth-form.tsx +4 -4
  11. package/templates/agency/app/dashboard/dashboard-client.tsx +110 -64
  12. package/templates/agency/app/dashboard/page.tsx +14 -16
  13. package/templates/agency/app/icon.svg +3 -0
  14. package/templates/agency/app/login/page.tsx +12 -14
  15. package/templates/agency/components/marketing.tsx +12 -5
  16. package/templates/agency/functions/seedStudioBackoffice.ts +42 -11
  17. package/templates/agency/lib/agency.ts +22 -0
  18. package/templates/agency/lib/demo.ts +118 -0
  19. package/templates/agency/lib/site.config.ts +19 -13
  20. package/templates/agency/public/favicon.ico +0 -0
  21. package/templates/agency/tests/agency.test.ts +57 -0
  22. package/templates/ai-chat/.env.example +13 -27
  23. package/templates/ai-chat/AGENTS.md +1 -1
  24. package/templates/ai-chat/README.md +53 -60
  25. package/templates/ai-chat/app/(chat)/c/[id]/page.tsx +19 -0
  26. package/templates/ai-chat/app/(chat)/chat-client.tsx +657 -622
  27. package/templates/ai-chat/app/(chat)/layout.tsx +6 -43
  28. package/templates/ai-chat/app/(chat)/not-found.tsx +6 -10
  29. package/templates/ai-chat/app/(chat)/page.tsx +7 -11
  30. package/templates/ai-chat/app/auth-form.tsx +45 -38
  31. package/templates/ai-chat/app/error.tsx +9 -12
  32. package/templates/ai-chat/app/globals.css +464 -102
  33. package/templates/ai-chat/app/layout.tsx +11 -19
  34. package/templates/ai-chat/app/login/page.tsx +39 -24
  35. package/templates/ai-chat/app.ts +118 -43
  36. package/templates/ai-chat/components/chat/composer.tsx +193 -0
  37. package/templates/ai-chat/components/chat/empty-state.tsx +65 -0
  38. package/templates/ai-chat/components/chat/icons.tsx +198 -0
  39. package/templates/ai-chat/components/chat/markdown.tsx +164 -0
  40. package/templates/ai-chat/components/chat/message.tsx +183 -0
  41. package/templates/ai-chat/components/chat/setup.tsx +71 -0
  42. package/templates/ai-chat/components/chat/sidebar.tsx +288 -0
  43. package/templates/ai-chat/functions/_beginTurn.ts +145 -0
  44. package/templates/ai-chat/functions/_finishTurn.ts +55 -0
  45. package/templates/ai-chat/functions/assistantStatus.ts +13 -0
  46. package/templates/ai-chat/functions/claimGuestHistory.ts +39 -0
  47. package/templates/ai-chat/functions/createConversation.ts +28 -0
  48. package/templates/ai-chat/functions/deleteConversation.ts +15 -20
  49. package/templates/ai-chat/functions/prepareGuestClaim.ts +32 -0
  50. package/templates/ai-chat/functions/renameConversation.ts +19 -0
  51. package/templates/ai-chat/functions/respond.ts +88 -0
  52. package/templates/ai-chat/functions/seedExamples.ts +46 -0
  53. package/templates/ai-chat/functions/stopResponse.ts +30 -0
  54. package/templates/ai-chat/lib/brand-icon.ts +7 -0
  55. package/templates/ai-chat/lib/chat.ts +277 -0
  56. package/templates/ai-chat/lib/claim.ts +15 -0
  57. package/templates/ai-chat/lib/examples.ts +179 -0
  58. package/templates/ai-chat/lib/highlight.ts +107 -0
  59. package/templates/ai-chat/lib/markdown.ts +317 -0
  60. package/templates/ai-chat/lib/provider.ts +38 -0
  61. package/templates/ai-chat/lib/site.config.ts +55 -39
  62. package/templates/ai-chat/tests/chat.test.ts +174 -0
  63. package/templates/ai-chat/tests/components.test.tsx +57 -0
  64. package/templates/ai-chat/tests/markdown.test.ts +88 -0
  65. package/templates/ai-chat/tests/provider.test.ts +32 -0
  66. package/templates/ai-studio/AGENTS.md +1 -1
  67. package/templates/backend/consumer/apps/api/app/layout.tsx +33 -0
  68. package/templates/backend/consumer/apps/api/app/page.tsx +54 -0
  69. package/templates/backend/consumer/apps/api/app.ts +166 -0
  70. package/templates/backend/consumer/apps/api/functions/addComment.ts +24 -0
  71. package/templates/backend/consumer/apps/api/functions/createPost.ts +19 -36
  72. package/templates/backend/consumer/apps/api/functions/deletePost.ts +15 -13
  73. package/templates/backend/consumer/apps/api/functions/myProfile.ts +5 -8
  74. package/templates/backend/consumer/apps/api/functions/seedDemo.ts +95 -0
  75. package/templates/backend/consumer/apps/api/functions/setFollow.ts +36 -0
  76. package/templates/backend/consumer/apps/api/functions/setLike.ts +36 -0
  77. package/templates/backend/consumer/apps/api/functions/upsertProfile.ts +43 -37
  78. package/templates/backend/consumer/apps/api/lib/seed.ts +89 -0
  79. package/templates/backend/consumer/apps/api/lib/social.ts +50 -0
  80. package/templates/backend/consumer/apps/api/package.json +8 -2
  81. package/templates/backend/consumer/apps/api/public/images/posts/arch.jpg +0 -0
  82. package/templates/backend/consumer/apps/api/public/images/posts/coffee.jpg +0 -0
  83. package/templates/backend/consumer/apps/api/public/images/posts/dog.jpg +0 -0
  84. package/templates/backend/consumer/apps/api/public/images/posts/lisbon.jpg +0 -0
  85. package/templates/backend/consumer/apps/api/public/images/posts/ramen.jpg +0 -0
  86. package/templates/backend/consumer/apps/api/public/images/posts/run.jpg +0 -0
  87. package/templates/backend/consumer/apps/api/public/images/posts/sunset.jpg +0 -0
  88. package/templates/backend/consumer/apps/api/tsconfig.json +12 -2
  89. package/templates/barebones/AGENTS.md +1 -1
  90. package/templates/chat/AGENTS.md +1 -1
  91. package/templates/chat/README.md +43 -21
  92. package/templates/chat/app/chat-client.tsx +495 -85
  93. package/templates/chat/app/error.tsx +1 -1
  94. package/templates/chat/app/globals.css +170 -54
  95. package/templates/chat/app/layout.tsx +12 -9
  96. package/templates/chat/app/not-found.tsx +4 -2
  97. package/templates/chat/app/page.tsx +9 -19
  98. package/templates/chat/app.ts +96 -17
  99. package/templates/chat/components/chat/avatar.tsx +45 -0
  100. package/templates/chat/components/chat/composer.tsx +110 -0
  101. package/templates/chat/components/chat/message-list.tsx +285 -0
  102. package/templates/chat/components/chat/sidebar.tsx +190 -0
  103. package/templates/chat/functions/joinChat.ts +83 -0
  104. package/templates/chat/functions/renameMember.ts +25 -0
  105. package/templates/chat/lib/chat.ts +188 -0
  106. package/templates/chat/lib/seed.ts +78 -0
  107. package/templates/chat/tests/chat.test.ts +159 -0
  108. package/templates/chat/tests/composer.test.tsx +54 -0
  109. package/templates/consumer/AGENTS.md +1 -1
  110. package/templates/consumer/README.md +3 -3
  111. package/templates/consumer/app/globals.css +10 -3
  112. package/templates/consumer/app/icon.svg +1 -0
  113. package/templates/consumer/components/social/avatar.tsx +25 -11
  114. package/templates/consumer/functions/seedFeed.ts +3 -3
  115. package/templates/consumer/lib/seed.ts +43 -85
  116. package/templates/consumer/public/favicon.ico +0 -0
  117. package/templates/consumer/public/images/posts/arch.jpg +0 -0
  118. package/templates/consumer/public/images/posts/coffee.jpg +0 -0
  119. package/templates/consumer/public/images/posts/dog.jpg +0 -0
  120. package/templates/consumer/public/images/posts/lisbon.jpg +0 -0
  121. package/templates/consumer/public/images/posts/ramen.jpg +0 -0
  122. package/templates/consumer/public/images/posts/run.jpg +0 -0
  123. package/templates/consumer/public/images/posts/sunset.jpg +0 -0
  124. package/templates/consumer/tests/seed.test.ts +39 -0
  125. package/templates/creator/.env.example +7 -0
  126. package/templates/creator/AGENTS.md +1 -1
  127. package/templates/creator/README.md +24 -5
  128. package/templates/creator/app/(marketing)/newsletter-signup.tsx +33 -12
  129. package/templates/creator/app/auth-form.tsx +7 -7
  130. package/templates/creator/app/dashboard/dashboard-client.tsx +137 -100
  131. package/templates/creator/app/dashboard/page.tsx +18 -18
  132. package/templates/creator/app/icon.svg +2 -0
  133. package/templates/creator/app/login/page.tsx +14 -14
  134. package/templates/creator/app.ts +4 -4
  135. package/templates/creator/functions/seedDemo.ts +32 -0
  136. package/templates/creator/functions/subscribe.ts +4 -15
  137. package/templates/creator/lib/demo.ts +94 -0
  138. package/templates/creator/lib/site.config.ts +15 -10
  139. package/templates/creator/lib/stats.ts +35 -0
  140. package/templates/creator/public/favicon.ico +0 -0
  141. package/templates/creator/tests/counter.test.ts +92 -0
  142. package/templates/creator/tests/live-count.test.tsx +45 -0
  143. package/templates/crm/AGENTS.md +1 -1
  144. package/templates/crm/README.md +22 -1
  145. package/templates/crm/app/apple-icon.png +0 -0
  146. package/templates/crm/app/companies/companies-view.tsx +32 -14
  147. package/templates/crm/app/companies/page.tsx +2 -1
  148. package/templates/crm/app/contacts/contacts-view.tsx +34 -10
  149. package/templates/crm/app/contacts/page.tsx +2 -1
  150. package/templates/crm/app/deals/[id]/deal-view.tsx +137 -98
  151. package/templates/crm/app/deals/[id]/page.tsx +2 -1
  152. package/templates/crm/app/globals.css +29 -14
  153. package/templates/crm/app/icon.svg +1 -0
  154. package/templates/crm/app/layout.tsx +7 -5
  155. package/templates/crm/app/page.tsx +2 -1
  156. package/templates/crm/app/pipeline-view.tsx +5 -5
  157. package/templates/crm/app/workspace.tsx +29 -17
  158. package/templates/crm/app.ts +5 -5
  159. package/templates/crm/components/activity-timeline.tsx +14 -4
  160. package/templates/crm/components/app-shell.tsx +94 -0
  161. package/templates/crm/components/auth-form.tsx +35 -5
  162. package/templates/crm/components/avatar.tsx +5 -1
  163. package/templates/crm/components/board-skeleton.tsx +5 -5
  164. package/templates/crm/components/brand-mark.tsx +25 -0
  165. package/templates/crm/components/command-palette.tsx +17 -17
  166. package/templates/crm/components/data-table.tsx +97 -36
  167. package/templates/crm/components/deal-card.tsx +23 -27
  168. package/templates/crm/components/filter-tabs.tsx +101 -0
  169. package/templates/crm/components/metrics-bar.tsx +6 -6
  170. package/templates/crm/components/nav.tsx +15 -0
  171. package/templates/crm/components/page-header.tsx +43 -7
  172. package/templates/crm/components/pipeline-board.tsx +16 -10
  173. package/templates/crm/components/require-auth.tsx +24 -26
  174. package/templates/crm/components/sidebar.tsx +54 -49
  175. package/templates/crm/components/stage-badge.tsx +4 -2
  176. package/templates/crm/components/ui/button.tsx +5 -4
  177. package/templates/crm/components/ui/dialog.tsx +1 -1
  178. package/templates/crm/components/ui/input.tsx +1 -1
  179. package/templates/crm/components/ui/select.tsx +1 -1
  180. package/templates/crm/components/ui/textarea.tsx +1 -1
  181. package/templates/crm/functions/seedWorkspace.ts +32 -17
  182. package/templates/crm/lib/brand.ts +9 -0
  183. package/templates/crm/lib/demo-team.ts +51 -0
  184. package/templates/crm/lib/pipeline.ts +37 -0
  185. package/templates/crm/lib/search.ts +4 -3
  186. package/templates/crm/lib/seed.ts +53 -8
  187. package/templates/crm/public/favicon.ico +0 -0
  188. package/templates/crm/tests/components.test.tsx +5 -5
  189. package/templates/crm/tests/demo-team.test.ts +18 -0
  190. package/templates/crm/tests/pipeline.test.ts +59 -3
  191. package/templates/directory/.env.example +7 -0
  192. package/templates/directory/AGENTS.md +1 -1
  193. package/templates/directory/README.md +22 -3
  194. package/templates/directory/app/(marketing)/directory-browse.tsx +30 -12
  195. package/templates/directory/app/(marketing)/page.tsx +6 -4
  196. package/templates/directory/app/auth-form.tsx +4 -4
  197. package/templates/directory/app/dashboard/dashboard-client.tsx +177 -90
  198. package/templates/directory/app/dashboard/page.tsx +16 -14
  199. package/templates/directory/app/icon.svg +4 -0
  200. package/templates/directory/app/login/page.tsx +16 -15
  201. package/templates/directory/functions/seedDemo.ts +43 -0
  202. package/templates/directory/lib/demo.ts +145 -0
  203. package/templates/directory/lib/directory.ts +27 -0
  204. package/templates/directory/lib/site.config.ts +21 -16
  205. package/templates/directory/public/favicon.ico +0 -0
  206. package/templates/directory/tests/directory.test.ts +46 -0
  207. package/templates/expo/consumer/apps/expo/App.tsx +360 -288
  208. package/templates/expo/consumer/apps/expo/app.json +9 -1
  209. package/templates/expo/consumer/apps/expo/package.json +2 -1
  210. package/templates/helpdesk/AGENTS.md +1 -1
  211. package/templates/helpdesk/README.md +21 -0
  212. package/templates/helpdesk/app/apple-icon.png +0 -0
  213. package/templates/helpdesk/app/customers/customers-view.tsx +23 -7
  214. package/templates/helpdesk/app/customers/page.tsx +2 -1
  215. package/templates/helpdesk/app/globals.css +29 -14
  216. package/templates/helpdesk/app/icon.svg +1 -0
  217. package/templates/helpdesk/app/inbox-view.tsx +66 -47
  218. package/templates/helpdesk/app/layout.tsx +7 -5
  219. package/templates/helpdesk/app/page.tsx +4 -7
  220. package/templates/helpdesk/app/tickets/[id]/page.tsx +2 -1
  221. package/templates/helpdesk/app/tickets/[id]/ticket-view.tsx +134 -97
  222. package/templates/helpdesk/app/workspace.tsx +33 -22
  223. package/templates/helpdesk/app.ts +2 -2
  224. package/templates/helpdesk/components/app-shell.tsx +94 -0
  225. package/templates/helpdesk/components/auth-form.tsx +35 -5
  226. package/templates/helpdesk/components/avatar.tsx +5 -1
  227. package/templates/helpdesk/components/brand-mark.tsx +28 -0
  228. package/templates/helpdesk/components/command-palette.tsx +17 -17
  229. package/templates/helpdesk/components/data-table.tsx +97 -36
  230. package/templates/helpdesk/components/filter-tabs.tsx +101 -0
  231. package/templates/helpdesk/components/nav.tsx +14 -0
  232. package/templates/helpdesk/components/page-header.tsx +43 -7
  233. package/templates/helpdesk/components/priority-badge.tsx +49 -6
  234. package/templates/helpdesk/components/require-auth.tsx +24 -26
  235. package/templates/helpdesk/components/sidebar.tsx +54 -48
  236. package/templates/helpdesk/components/sla-indicator.tsx +5 -8
  237. package/templates/helpdesk/components/thread.tsx +7 -7
  238. package/templates/helpdesk/components/ticket-list.tsx +79 -19
  239. package/templates/helpdesk/components/ui/button.tsx +5 -4
  240. package/templates/helpdesk/components/ui/dialog.tsx +1 -1
  241. package/templates/helpdesk/components/ui/input.tsx +1 -1
  242. package/templates/helpdesk/components/ui/select.tsx +1 -1
  243. package/templates/helpdesk/components/ui/textarea.tsx +1 -1
  244. package/templates/helpdesk/functions/seedWorkspace.ts +25 -15
  245. package/templates/helpdesk/lib/brand.ts +9 -0
  246. package/templates/helpdesk/lib/demo-team.ts +51 -0
  247. package/templates/helpdesk/lib/search.ts +4 -3
  248. package/templates/helpdesk/lib/seed.ts +43 -2
  249. package/templates/helpdesk/lib/tickets.ts +68 -20
  250. package/templates/helpdesk/public/favicon.ico +0 -0
  251. package/templates/helpdesk/tests/components.test.tsx +10 -0
  252. package/templates/helpdesk/tests/demo-team.test.ts +18 -0
  253. package/templates/helpdesk/tests/tickets.test.ts +70 -0
  254. package/templates/imessage/.env.example +49 -0
  255. package/templates/imessage/AGENTS.md +153 -0
  256. package/templates/imessage/CLAUDE.md +7 -0
  257. package/templates/imessage/README.md +187 -0
  258. package/templates/imessage/app/console.tsx +382 -0
  259. package/templates/imessage/app/contacts/page.tsx +13 -0
  260. package/templates/imessage/app/error.tsx +16 -0
  261. package/templates/imessage/app/gate.tsx +31 -0
  262. package/templates/imessage/app/globals.css +183 -0
  263. package/templates/imessage/app/layout.tsx +17 -0
  264. package/templates/imessage/app/login/login-form.tsx +115 -0
  265. package/templates/imessage/app/login/page.tsx +30 -0
  266. package/templates/imessage/app/not-found.tsx +13 -0
  267. package/templates/imessage/app/page.tsx +14 -0
  268. package/templates/imessage/app/robots.ts +6 -0
  269. package/templates/imessage/app/setup/page.tsx +13 -0
  270. package/templates/imessage/app/sign-out.tsx +20 -0
  271. package/templates/imessage/app.ts +299 -0
  272. package/templates/imessage/bunfig +4 -0
  273. package/templates/imessage/components/avatar.tsx +46 -0
  274. package/templates/imessage/components/contact-panel.tsx +263 -0
  275. package/templates/imessage/components/contacts-view.tsx +172 -0
  276. package/templates/imessage/components/conversation-list.tsx +129 -0
  277. package/templates/imessage/components/nav-rail.tsx +67 -0
  278. package/templates/imessage/components/setup-view.tsx +408 -0
  279. package/templates/imessage/components/thread.tsx +229 -0
  280. package/templates/imessage/components/ui/badge.tsx +37 -0
  281. package/templates/imessage/components/ui/button.tsx +56 -0
  282. package/templates/imessage/components/ui/card.tsx +90 -0
  283. package/templates/imessage/components/ui/dialog.tsx +110 -0
  284. package/templates/imessage/components/ui/input.tsx +30 -0
  285. package/templates/imessage/components/ui/label.tsx +21 -0
  286. package/templates/imessage/components/ui/select.tsx +39 -0
  287. package/templates/imessage/components/ui/table.tsx +124 -0
  288. package/templates/imessage/components/ui/textarea.tsx +24 -0
  289. package/templates/imessage/components.json +20 -0
  290. package/templates/imessage/functions/answerNow.ts +34 -0
  291. package/templates/imessage/functions/assistant.ts +96 -0
  292. package/templates/imessage/functions/beginTurn.ts +101 -0
  293. package/templates/imessage/functions/cancelReminder.ts +13 -0
  294. package/templates/imessage/functions/claimOutbound.ts +44 -0
  295. package/templates/imessage/functions/clearDemo.ts +18 -0
  296. package/templates/imessage/functions/deleteNote.ts +11 -0
  297. package/templates/imessage/functions/deliverOutbound.ts +55 -0
  298. package/templates/imessage/functions/finishTurn.ts +84 -0
  299. package/templates/imessage/functions/fireReminder.ts +42 -0
  300. package/templates/imessage/functions/ingestInbound.ts +22 -0
  301. package/templates/imessage/functions/ownerAccount.ts +13 -0
  302. package/templates/imessage/functions/processTurn.ts +74 -0
  303. package/templates/imessage/functions/relayApply.ts +102 -0
  304. package/templates/imessage/functions/relaySync.ts +63 -0
  305. package/templates/imessage/functions/releaseOutbound.ts +18 -0
  306. package/templates/imessage/functions/resolveTurn.ts +25 -0
  307. package/templates/imessage/functions/saveContact.ts +27 -0
  308. package/templates/imessage/functions/seedDemo.ts +70 -0
  309. package/templates/imessage/functions/sendOwnerMessage.ts +28 -0
  310. package/templates/imessage/functions/sendblueWebhook.ts +49 -0
  311. package/templates/imessage/functions/setContactStatus.ts +17 -0
  312. package/templates/imessage/functions/settleOutbound.ts +35 -0
  313. package/templates/imessage/functions/setupStatus.ts +33 -0
  314. package/templates/imessage/functions/sweep.ts +58 -0
  315. package/templates/imessage/functions/toolCancelReminder.ts +17 -0
  316. package/templates/imessage/functions/toolForgetNote.ts +15 -0
  317. package/templates/imessage/functions/toolListNotes.ts +16 -0
  318. package/templates/imessage/functions/toolListReminders.ts +17 -0
  319. package/templates/imessage/functions/toolSaveNote.ts +22 -0
  320. package/templates/imessage/functions/toolSetReminder.ts +55 -0
  321. package/templates/imessage/functions/updateSettings.ts +48 -0
  322. package/templates/imessage/gitignore +11 -0
  323. package/templates/imessage/lib/agent-call.ts +251 -0
  324. package/templates/imessage/lib/assistant.ts +74 -0
  325. package/templates/imessage/lib/chunk.ts +135 -0
  326. package/templates/imessage/lib/demo.ts +68 -0
  327. package/templates/imessage/lib/format.ts +170 -0
  328. package/templates/imessage/lib/gate.ts +104 -0
  329. package/templates/imessage/lib/handles.ts +67 -0
  330. package/templates/imessage/lib/provider.ts +38 -0
  331. package/templates/imessage/lib/relay-protocol.ts +95 -0
  332. package/templates/imessage/lib/reminders.ts +41 -0
  333. package/templates/imessage/lib/secrets.ts +59 -0
  334. package/templates/imessage/lib/sendblue.ts +173 -0
  335. package/templates/imessage/lib/store.ts +407 -0
  336. package/templates/imessage/lib/transport.ts +135 -0
  337. package/templates/imessage/lib/utils.ts +10 -0
  338. package/templates/imessage/package.json +40 -0
  339. package/templates/imessage/relay/.env.example +14 -0
  340. package/templates/imessage/relay/applescript.ts +70 -0
  341. package/templates/imessage/relay/chatdb.ts +165 -0
  342. package/templates/imessage/relay/client.ts +41 -0
  343. package/templates/imessage/relay/main.ts +137 -0
  344. package/templates/imessage/relay/state.ts +35 -0
  345. package/templates/imessage/tests/agent-call.test.ts +112 -0
  346. package/templates/imessage/tests/applescript.test.ts +88 -0
  347. package/templates/imessage/tests/chatdb.test.ts +139 -0
  348. package/templates/imessage/tests/chunk.test.ts +61 -0
  349. package/templates/imessage/tests/fake-ctx.ts +88 -0
  350. package/templates/imessage/tests/ingest.test.ts +141 -0
  351. package/templates/imessage/tests/lib.test.ts +99 -0
  352. package/templates/imessage/tests/review-fixes.test.ts +138 -0
  353. package/templates/imessage/tests/secrets.test.ts +36 -0
  354. package/templates/imessage/tests/sendblue.test.ts +143 -0
  355. package/templates/imessage/tests/setup.ts +5 -0
  356. package/templates/imessage/tsconfig.json +18 -0
  357. package/templates/inventory/AGENTS.md +1 -1
  358. package/templates/inventory/README.md +21 -0
  359. package/templates/inventory/app/apple-icon.png +0 -0
  360. package/templates/inventory/app/globals.css +29 -14
  361. package/templates/inventory/app/icon.svg +1 -0
  362. package/templates/inventory/app/layout.tsx +7 -5
  363. package/templates/inventory/app/movements/movements-view.tsx +32 -10
  364. package/templates/inventory/app/movements/page.tsx +2 -1
  365. package/templates/inventory/app/page.tsx +4 -6
  366. package/templates/inventory/app/products/[id]/page.tsx +2 -1
  367. package/templates/inventory/app/products/[id]/product-view.tsx +55 -25
  368. package/templates/inventory/app/products-view.tsx +74 -33
  369. package/templates/inventory/app/workspace.tsx +34 -16
  370. package/templates/inventory/app.ts +10 -2
  371. package/templates/inventory/components/app-shell.tsx +94 -0
  372. package/templates/inventory/components/auth-form.tsx +35 -5
  373. package/templates/inventory/components/avatar.tsx +5 -1
  374. package/templates/inventory/components/brand-mark.tsx +25 -0
  375. package/templates/inventory/components/category-tile.tsx +49 -0
  376. package/templates/inventory/components/command-palette.tsx +17 -17
  377. package/templates/inventory/components/data-table.tsx +97 -36
  378. package/templates/inventory/components/filter-tabs.tsx +101 -0
  379. package/templates/inventory/components/nav.tsx +14 -0
  380. package/templates/inventory/components/page-header.tsx +43 -7
  381. package/templates/inventory/components/require-auth.tsx +24 -26
  382. package/templates/inventory/components/sidebar.tsx +54 -48
  383. package/templates/inventory/components/stock-level.tsx +30 -14
  384. package/templates/inventory/components/summary-bar.tsx +8 -15
  385. package/templates/inventory/components/ui/button.tsx +5 -4
  386. package/templates/inventory/components/ui/dialog.tsx +1 -1
  387. package/templates/inventory/components/ui/input.tsx +1 -1
  388. package/templates/inventory/components/ui/select.tsx +1 -1
  389. package/templates/inventory/components/ui/textarea.tsx +1 -1
  390. package/templates/inventory/functions/seedWorkspace.ts +18 -8
  391. package/templates/inventory/lib/brand.ts +9 -0
  392. package/templates/inventory/lib/demo-team.ts +51 -0
  393. package/templates/inventory/lib/search.ts +4 -3
  394. package/templates/inventory/lib/seed.ts +29 -2
  395. package/templates/inventory/lib/stock.ts +32 -0
  396. package/templates/inventory/public/favicon.ico +0 -0
  397. package/templates/inventory/tests/demo-team.test.ts +18 -0
  398. package/templates/inventory/tests/stock.test.ts +29 -0
  399. package/templates/invoices/AGENTS.md +1 -1
  400. package/templates/invoices/README.md +16 -0
  401. package/templates/invoices/app/apple-icon.png +0 -0
  402. package/templates/invoices/app/clients/clients-view.tsx +18 -6
  403. package/templates/invoices/app/clients/page.tsx +2 -1
  404. package/templates/invoices/app/globals.css +29 -14
  405. package/templates/invoices/app/icon.svg +1 -0
  406. package/templates/invoices/app/invoices/[id]/invoice-view.tsx +72 -48
  407. package/templates/invoices/app/invoices/[id]/page.tsx +2 -1
  408. package/templates/invoices/app/invoices-view.tsx +81 -32
  409. package/templates/invoices/app/layout.tsx +7 -5
  410. package/templates/invoices/app/page.tsx +4 -6
  411. package/templates/invoices/app/workspace.tsx +26 -16
  412. package/templates/invoices/app.ts +2 -2
  413. package/templates/invoices/components/app-shell.tsx +94 -0
  414. package/templates/invoices/components/auth-form.tsx +35 -5
  415. package/templates/invoices/components/avatar.tsx +5 -1
  416. package/templates/invoices/components/brand-mark.tsx +26 -0
  417. package/templates/invoices/components/command-palette.tsx +17 -17
  418. package/templates/invoices/components/data-table.tsx +97 -36
  419. package/templates/invoices/components/filter-tabs.tsx +101 -0
  420. package/templates/invoices/components/line-items.tsx +19 -14
  421. package/templates/invoices/components/nav.tsx +14 -0
  422. package/templates/invoices/components/page-header.tsx +43 -7
  423. package/templates/invoices/components/require-auth.tsx +24 -26
  424. package/templates/invoices/components/sidebar.tsx +54 -48
  425. package/templates/invoices/components/status-badge.tsx +7 -7
  426. package/templates/invoices/components/summary-bar.tsx +5 -5
  427. package/templates/invoices/components/ui/button.tsx +5 -4
  428. package/templates/invoices/components/ui/dialog.tsx +1 -1
  429. package/templates/invoices/components/ui/input.tsx +1 -1
  430. package/templates/invoices/components/ui/select.tsx +1 -1
  431. package/templates/invoices/components/ui/textarea.tsx +1 -1
  432. package/templates/invoices/functions/recordPayment.ts +3 -0
  433. package/templates/invoices/lib/billing.ts +40 -0
  434. package/templates/invoices/lib/brand.ts +9 -0
  435. package/templates/invoices/lib/search.ts +4 -3
  436. package/templates/invoices/public/favicon.ico +0 -0
  437. package/templates/invoices/tests/billing.test.ts +27 -0
  438. package/templates/ios/consumer/apps/ios/Package.swift +4 -3
  439. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ActivityView.swift +107 -0
  440. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/AppModel.swift +171 -0
  441. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/AuthView.swift +182 -0
  442. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/CommentsView.swift +136 -0
  443. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ComposeView.swift +152 -0
  444. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ExploreView.swift +117 -0
  445. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/FeedView.swift +175 -159
  446. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/MainTabView.swift +54 -0
  447. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/Media.swift +271 -0
  448. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/Models.swift +101 -4
  449. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/PostCard.swift +245 -0
  450. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ProfileEditorView.swift +208 -0
  451. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ProfileView.swift +183 -0
  452. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/RootView.swift +25 -31
  453. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/SocialStore.swift +285 -0
  454. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/StoryViewer.swift +155 -0
  455. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/Theme.swift +82 -0
  456. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/__APP_NAME_PASCAL__App.swift +4 -47
  457. package/templates/ios/consumer/apps/ios/project.yml +13 -9
  458. package/templates/local-service/.env.example +7 -0
  459. package/templates/local-service/AGENTS.md +1 -1
  460. package/templates/local-service/README.md +26 -2
  461. package/templates/local-service/app/(marketing)/booking-widget.tsx +48 -9
  462. package/templates/local-service/app/(marketing)/page.tsx +66 -42
  463. package/templates/local-service/app/auth-form.tsx +8 -8
  464. package/templates/local-service/app/dashboard/dashboard-client.tsx +119 -92
  465. package/templates/local-service/app/dashboard/page.tsx +14 -14
  466. package/templates/local-service/app/icon.svg +2 -0
  467. package/templates/local-service/app/login/page.tsx +15 -15
  468. package/templates/local-service/functions/seedDemo.ts +49 -0
  469. package/templates/local-service/lib/demo.ts +135 -0
  470. package/templates/local-service/lib/hours.ts +44 -0
  471. package/templates/local-service/lib/site.config.ts +8 -3
  472. package/templates/local-service/lib/slots.ts +3 -0
  473. package/templates/local-service/public/favicon.ico +0 -0
  474. package/templates/local-service/tests/booking.test.ts +104 -0
  475. package/templates/marketplace/AGENTS.md +1 -1
  476. package/templates/marketplace/README.md +23 -15
  477. package/templates/marketplace/app/globals.css +172 -114
  478. package/templates/marketplace/app/layout.tsx +75 -30
  479. package/templates/marketplace/app/listing/[id]/page.tsx +210 -123
  480. package/templates/marketplace/app/me/page.tsx +4 -4
  481. package/templates/marketplace/app/page.tsx +221 -298
  482. package/templates/marketplace/app/sell/page.tsx +8 -11
  483. package/templates/marketplace/app.ts +32 -7
  484. package/templates/marketplace/client/ActivityFeed.tsx +311 -0
  485. package/templates/marketplace/client/AuthNav.tsx +24 -16
  486. package/templates/marketplace/client/ListingCard.tsx +118 -0
  487. package/templates/marketplace/client/LiveGrid.tsx +92 -0
  488. package/templates/marketplace/client/LiveListingStatus.tsx +32 -24
  489. package/templates/marketplace/client/LoginCard.tsx +114 -136
  490. package/templates/marketplace/client/MarketProvider.tsx +3 -5
  491. package/templates/marketplace/client/MyMarket.tsx +371 -185
  492. package/templates/marketplace/client/OfferPanel.tsx +421 -287
  493. package/templates/marketplace/client/SellForm.tsx +271 -192
  494. package/templates/marketplace/client/ThemeToggle.tsx +1 -0
  495. package/templates/marketplace/client/WatchButton.tsx +16 -16
  496. package/templates/marketplace/client/market.ts +61 -14
  497. package/templates/marketplace/functions/seedMarket.ts +143 -36
  498. package/templates/marketplace/functions/seedOffers.ts +101 -0
  499. package/templates/marketplace/lib/catalog.ts +72 -1
  500. package/templates/marketplace/public/images/listings/ceramic-lamp-640.webp +0 -0
  501. package/templates/marketplace/public/images/listings/ceramic-lamp.webp +0 -0
  502. package/templates/marketplace/public/images/listings/duffel-640.webp +0 -0
  503. package/templates/marketplace/public/images/listings/duffel.webp +0 -0
  504. package/templates/marketplace/public/images/listings/film-camera-640.webp +0 -0
  505. package/templates/marketplace/public/images/listings/film-camera.webp +0 -0
  506. package/templates/marketplace/public/images/listings/lounge-chair-640.webp +0 -0
  507. package/templates/marketplace/public/images/listings/lounge-chair.webp +0 -0
  508. package/templates/marketplace/public/images/listings/road-bike-640.webp +0 -0
  509. package/templates/marketplace/public/images/listings/road-bike.webp +0 -0
  510. package/templates/marketplace/public/images/listings/slipper-chair-640.webp +0 -0
  511. package/templates/marketplace/public/images/listings/slipper-chair.webp +0 -0
  512. package/templates/marketplace/public/images/listings/turntable-640.webp +0 -0
  513. package/templates/marketplace/public/images/listings/turntable.webp +0 -0
  514. package/templates/marketplace/public/images/listings/wardrobe-640.webp +0 -0
  515. package/templates/marketplace/public/images/listings/wardrobe.webp +0 -0
  516. package/templates/marketplace/public/images/listings/watch-640.webp +0 -0
  517. package/templates/marketplace/public/images/listings/watch.webp +0 -0
  518. package/templates/marketplace/public/images/listings/weekender-bag-640.webp +0 -0
  519. package/templates/marketplace/public/images/listings/weekender-bag.webp +0 -0
  520. package/templates/marketplace/tests/example.test.ts +64 -2
  521. package/templates/marketplace/tests/listing-card.test.tsx +61 -0
  522. package/templates/projects/AGENTS.md +1 -1
  523. package/templates/projects/README.md +24 -3
  524. package/templates/projects/app/apple-icon.png +0 -0
  525. package/templates/projects/app/clients/clients-view.tsx +20 -6
  526. package/templates/projects/app/clients/page.tsx +2 -1
  527. package/templates/projects/app/globals.css +29 -14
  528. package/templates/projects/app/icon.svg +1 -0
  529. package/templates/projects/app/layout.tsx +7 -5
  530. package/templates/projects/app/page.tsx +3 -5
  531. package/templates/projects/app/projects/[id]/page.tsx +2 -1
  532. package/templates/projects/app/projects/[id]/project-view.tsx +45 -30
  533. package/templates/projects/app/projects-view.tsx +105 -52
  534. package/templates/projects/app/workspace.tsx +41 -20
  535. package/templates/projects/app.ts +2 -2
  536. package/templates/projects/components/app-shell.tsx +94 -0
  537. package/templates/projects/components/auth-form.tsx +35 -5
  538. package/templates/projects/components/avatar.tsx +5 -1
  539. package/templates/projects/components/board-skeleton.tsx +3 -3
  540. package/templates/projects/components/brand-mark.tsx +25 -0
  541. package/templates/projects/components/command-palette.tsx +17 -17
  542. package/templates/projects/components/data-table.tsx +97 -36
  543. package/templates/projects/components/filter-tabs.tsx +101 -0
  544. package/templates/projects/components/meter.tsx +48 -0
  545. package/templates/projects/components/nav.tsx +14 -0
  546. package/templates/projects/components/page-header.tsx +43 -7
  547. package/templates/projects/components/require-auth.tsx +24 -26
  548. package/templates/projects/components/sidebar.tsx +54 -48
  549. package/templates/projects/components/summary-bar.tsx +49 -0
  550. package/templates/projects/components/task-board.tsx +10 -9
  551. package/templates/projects/components/time-dialog.tsx +29 -5
  552. package/templates/projects/components/ui/button.tsx +5 -4
  553. package/templates/projects/components/ui/dialog.tsx +1 -1
  554. package/templates/projects/components/ui/input.tsx +1 -1
  555. package/templates/projects/components/ui/select.tsx +1 -1
  556. package/templates/projects/components/ui/textarea.tsx +1 -1
  557. package/templates/projects/functions/seedWorkspace.ts +26 -12
  558. package/templates/projects/lib/brand.ts +9 -0
  559. package/templates/projects/lib/demo-team.ts +51 -0
  560. package/templates/projects/lib/search.ts +4 -3
  561. package/templates/projects/lib/seed.ts +35 -1
  562. package/templates/projects/lib/work.ts +63 -0
  563. package/templates/projects/public/favicon.ico +0 -0
  564. package/templates/projects/tests/demo-team.test.ts +18 -0
  565. package/templates/projects/tests/work.test.ts +65 -0
  566. package/templates/restaurant/.env.example +7 -0
  567. package/templates/restaurant/AGENTS.md +1 -1
  568. package/templates/restaurant/README.md +29 -3
  569. package/templates/restaurant/app/(marketing)/layout.tsx +4 -2
  570. package/templates/restaurant/app/(marketing)/page.tsx +47 -23
  571. package/templates/restaurant/app/(marketing)/reservation-widget.tsx +37 -7
  572. package/templates/restaurant/app/auth-form.tsx +8 -8
  573. package/templates/restaurant/app/dashboard/dashboard-client.tsx +241 -109
  574. package/templates/restaurant/app/dashboard/page.tsx +14 -14
  575. package/templates/restaurant/app/icon.svg +4 -0
  576. package/templates/restaurant/app/login/page.tsx +15 -15
  577. package/templates/restaurant/functions/seedDemo.ts +44 -0
  578. package/templates/restaurant/lib/demo.ts +144 -0
  579. package/templates/restaurant/lib/hours.ts +45 -0
  580. package/templates/restaurant/lib/site.config.ts +3 -3
  581. package/templates/restaurant/public/favicon.ico +0 -0
  582. package/templates/restaurant/tests/reservations.test.ts +67 -0
  583. package/templates/saas/AGENTS.md +1 -1
  584. package/templates/saas/README.md +54 -35
  585. package/templates/saas/app/(auth)/auth-form.tsx +25 -23
  586. package/templates/saas/app/(auth)/forgot-password/page.tsx +1 -1
  587. package/templates/saas/app/(auth)/layout.tsx +27 -33
  588. package/templates/saas/app/(auth)/login/page.tsx +1 -1
  589. package/templates/saas/app/(auth)/reset-password/page.tsx +1 -1
  590. package/templates/saas/app/(auth)/signup/page.tsx +1 -1
  591. package/templates/saas/app/(marketing)/layout.tsx +127 -253
  592. package/templates/saas/app/(marketing)/page.tsx +133 -372
  593. package/templates/saas/app/(marketing)/pricing/page.tsx +21 -19
  594. package/templates/saas/app/(marketing)/products/[slug]/page.tsx +40 -95
  595. package/templates/saas/app/dashboard/board-client.tsx +416 -0
  596. package/templates/saas/app/dashboard/dashboard-client.tsx +30 -615
  597. package/templates/saas/app/dashboard/layout.tsx +27 -13
  598. package/templates/saas/app/dashboard/overview-client.tsx +332 -0
  599. package/templates/saas/app/dashboard/page.tsx +29 -35
  600. package/templates/saas/app/dashboard/projects/[id]/page.tsx +29 -0
  601. package/templates/saas/app/dashboard/projects/page.tsx +11 -7
  602. package/templates/saas/app/dashboard/projects-client.tsx +398 -0
  603. package/templates/saas/app/globals.css +5 -4
  604. package/templates/saas/app/icon.svg +1 -0
  605. package/templates/saas/app/onboarding/onboarding-client.tsx +93 -26
  606. package/templates/saas/app/onboarding/page.tsx +6 -2
  607. package/templates/saas/app.ts +68 -13
  608. package/templates/saas/components/dashboard-shell.tsx +212 -132
  609. package/templates/saas/components/marketing.tsx +137 -274
  610. package/templates/saas/components/pricing-table.tsx +12 -11
  611. package/templates/saas/components/setup-checklist.tsx +31 -29
  612. package/templates/saas/components/task-ui.tsx +199 -0
  613. package/templates/saas/functions/createProject.ts +7 -13
  614. package/templates/saas/functions/createTask.ts +67 -0
  615. package/templates/saas/functions/deleteProject.ts +17 -0
  616. package/templates/saas/functions/deleteTask.ts +14 -0
  617. package/templates/saas/functions/seedWorkspace.ts +46 -0
  618. package/templates/saas/functions/setProjectStatus.ts +41 -0
  619. package/templates/saas/functions/updateProfile.ts +13 -0
  620. package/templates/saas/functions/updateProject.ts +4 -5
  621. package/templates/saas/functions/updateTask.ts +88 -0
  622. package/templates/saas/lib/members.ts +53 -0
  623. package/templates/saas/lib/names.ts +53 -0
  624. package/templates/saas/lib/plans.ts +11 -0
  625. package/templates/saas/lib/sample-workspace.ts +117 -0
  626. package/templates/saas/lib/site.config.ts +200 -463
  627. package/templates/saas/lib/tasks.ts +101 -0
  628. package/templates/saas/lib/use-live.ts +25 -0
  629. package/templates/saas/public/favicon.ico +0 -0
  630. package/templates/saas/public/screenshots/board.png +0 -0
  631. package/templates/saas/public/screenshots/members.png +0 -0
  632. package/templates/saas/public/screenshots/overview.png +0 -0
  633. package/templates/saas/public/screenshots/projects.png +0 -0
  634. package/templates/saas/scripts/capture-screenshots.mjs +156 -0
  635. package/templates/saas/tests/names.test.ts +35 -0
  636. package/templates/saas/tests/plans.test.ts +12 -0
  637. package/templates/saas/tests/sample-workspace.test.ts +46 -0
  638. package/templates/saas/tests/tasks.test.ts +81 -0
  639. package/templates/shop/AGENTS.md +1 -1
  640. package/templates/shop/README.md +25 -13
  641. package/templates/shop/app/(marketing)/layout.tsx +94 -67
  642. package/templates/shop/app/(marketing)/not-found.tsx +13 -12
  643. package/templates/shop/app/(marketing)/page.tsx +120 -59
  644. package/templates/shop/app/(marketing)/shop-client.tsx +364 -237
  645. package/templates/shop/app/(marketing)/success/page.tsx +75 -22
  646. package/templates/shop/app/globals.css +94 -1
  647. package/templates/shop/app/layout.tsx +4 -4
  648. package/templates/shop/app/login/page.tsx +4 -4
  649. package/templates/shop/app.ts +18 -6
  650. package/templates/shop/components/cart-button.tsx +33 -0
  651. package/templates/shop/components/marketing.tsx +17 -87
  652. package/templates/shop/components/shelf-count.tsx +36 -0
  653. package/templates/shop/functions/checkout.ts +17 -1
  654. package/templates/shop/lib/cart-store.ts +57 -0
  655. package/templates/shop/lib/shelf-store.ts +35 -0
  656. package/templates/shop/lib/shop.ts +27 -0
  657. package/templates/shop/lib/site.config.ts +108 -98
  658. package/templates/shop/public/images/hero.jpg +0 -0
  659. package/templates/shop/public/images/products/cedar-smoke.jpg +0 -0
  660. package/templates/shop/public/images/products/fig-leaf.jpg +0 -0
  661. package/templates/shop/public/images/products/linen.jpg +0 -0
  662. package/templates/shop/public/images/products/sea-salt.jpg +0 -0
  663. package/templates/shop/public/images/products/tobacco-oak.jpg +0 -0
  664. package/templates/shop/tests/shop.test.ts +64 -0
  665. package/templates/todo/AGENTS.md +1 -1
  666. package/templates/waitlist/.env.example +7 -0
  667. package/templates/waitlist/AGENTS.md +1 -1
  668. package/templates/waitlist/README.md +29 -13
  669. package/templates/waitlist/app/(marketing)/page.tsx +9 -7
  670. package/templates/waitlist/app/(marketing)/waitlist-hero.tsx +64 -31
  671. package/templates/waitlist/app/auth-form.tsx +8 -8
  672. package/templates/waitlist/app/dashboard/dashboard-client.tsx +132 -95
  673. package/templates/waitlist/app/dashboard/page.tsx +17 -16
  674. package/templates/waitlist/app/icon.svg +2 -0
  675. package/templates/waitlist/app/login/page.tsx +14 -14
  676. package/templates/waitlist/app.ts +6 -6
  677. package/templates/waitlist/functions/joinWaitlist.ts +6 -18
  678. package/templates/waitlist/functions/seedDemo.ts +32 -0
  679. package/templates/waitlist/lib/demo.ts +94 -0
  680. package/templates/waitlist/lib/launch.ts +19 -0
  681. package/templates/waitlist/lib/site.config.ts +14 -9
  682. package/templates/waitlist/lib/stats.ts +35 -0
  683. package/templates/waitlist/public/favicon.ico +0 -0
  684. package/templates/waitlist/tests/counter.test.ts +107 -0
  685. package/templates/waitlist/tests/live-count.test.tsx +48 -0
  686. package/templates/ai-chat/tests/example.test.ts +0 -12
  687. package/templates/backend/consumer/apps/api/functions/feed.ts +0 -57
  688. package/templates/backend/consumer/apps/api/functions/profilePosts.ts +0 -17
  689. package/templates/backend/consumer/apps/api/functions/toggleLike.ts +0 -48
  690. package/templates/backend/consumer/apps/api/schema.ts +0 -143
  691. package/templates/chat/functions/_keep.ts +0 -13
  692. package/templates/chat/tests/example.test.ts +0 -12
  693. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ProfileSetupView.swift +0 -68
  694. package/templates/marketplace/client/LiveTicker.tsx +0 -116
  695. package/templates/marketplace/client/ScrollToListingsLink.tsx +0 -29
  696. package/templates/marketplace/public/images/marketplace-hero.webp +0 -0
@@ -65,6 +65,39 @@ export interface TimeEntry {
65
65
  spentOn?: string | null;
66
66
  }
67
67
 
68
+ export interface ProjectTab {
69
+ id: string;
70
+ label: string;
71
+ /** Project status the tab shows; undefined shows every project. */
72
+ status?: string;
73
+ }
74
+
75
+ /** The project list's tabs, left to right. */
76
+ export const PROJECT_TABS: ProjectTab[] = [
77
+ { id: "active", label: "Active", status: "active" },
78
+ { id: "paused", label: "Paused", status: "paused" },
79
+ { id: "complete", label: "Complete", status: "complete" },
80
+ { id: "all", label: "All" },
81
+ ];
82
+
83
+ /** The tab the list opens on. The sidebar's Projects count is this tab's count. */
84
+ export const DEFAULT_PROJECT_TAB = "active";
85
+
86
+ export function inProjectTab(tabId: string, project: { status: string }): boolean {
87
+ const tab = PROJECT_TABS.find((t) => t.id === tabId);
88
+ return !tab?.status || project.status === tab.status;
89
+ }
90
+
91
+ /**
92
+ * How many projects each tab lists. The sidebar badge, the page header, and
93
+ * the tab counts all read this, so they always agree.
94
+ */
95
+ export function projectTabCounts(projects: Array<{ status: string }>): Record<string, number> {
96
+ return Object.fromEntries(
97
+ PROJECT_TABS.map((tab) => [tab.id, projects.filter((p) => inProjectTab(tab.id, p)).length]),
98
+ );
99
+ }
100
+
68
101
  export function taskStatusById(id: string): Status | undefined {
69
102
  return TASK_STATUSES.find((s) => s.id === id);
70
103
  }
@@ -231,3 +264,33 @@ export function parseDuration(input: string): number | null {
231
264
 
232
265
  return null;
233
266
  }
267
+
268
+ export interface Portfolio {
269
+ /** Projects with status "active". */
270
+ activeCount: number;
271
+ /** Minutes logged against active projects. */
272
+ loggedMinutes: number;
273
+ /** What that time is worth at each project's rate. */
274
+ billableCents: number;
275
+ /** Active projects past their budget. */
276
+ overBudgetCount: number;
277
+ }
278
+
279
+ /** The numbers above the project list, over active projects only. */
280
+ export function portfolio(projects: Project[], entries: TimeEntry[]): Portfolio {
281
+ const result: Portfolio = {
282
+ activeCount: 0,
283
+ loggedMinutes: 0,
284
+ billableCents: 0,
285
+ overBudgetCount: 0,
286
+ };
287
+ for (const project of projects) {
288
+ if (project.status !== "active") continue;
289
+ const minutes = minutesForProject(project.id, entries);
290
+ result.activeCount += 1;
291
+ result.loggedMinutes += minutes;
292
+ result.billableCents += billableCents(minutes, project.hourlyRateCents);
293
+ if (budgetState(project.budgetMinutes, minutes) === "over") result.overBudgetCount += 1;
294
+ }
295
+ return result;
296
+ }
@@ -0,0 +1,18 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { demoEmail, isDemoRow } from "../lib/demo-team";
3
+
4
+ describe("demo teammates", () => {
5
+ test("the first address is the person's own, then numbered aliases", () => {
6
+ expect(demoEmail("maya.castillo@demo.invalid", 0)).toBe("maya.castillo@demo.invalid");
7
+ expect(demoEmail("maya.castillo@demo.invalid", 1)).toBe("maya.castillo+2@demo.invalid");
8
+ expect(demoEmail("maya.castillo@demo.invalid", 2)).toBe("maya.castillo+3@demo.invalid");
9
+ });
10
+
11
+ test("a registered account is never reused as a demo teammate", () => {
12
+ // Someone who registered a demo address first must not be handed the
13
+ // seeded records.
14
+ expect(isDemoRow({ email: "x@demo.invalid", passwordHash: "argon2id$..." })).toBe(false);
15
+ expect(isDemoRow({ email: "x@demo.invalid", emailVerified: "2026-07-01T00:00:00Z" })).toBe(false);
16
+ expect(isDemoRow({ email: "x@demo.invalid", displayName: "Maya Castillo" })).toBe(true);
17
+ });
18
+ });
@@ -1,10 +1,13 @@
1
1
  import { describe, expect, test } from "bun:test";
2
2
  import {
3
+ DEFAULT_PROJECT_TAB,
4
+ PROJECT_TABS,
3
5
  TASK_STATUSES,
4
6
  billableCents,
5
7
  budgetState,
6
8
  duration,
7
9
  groupByStatus,
10
+ inProjectTab,
8
11
  isOpen,
9
12
  isValidProjectStatus,
10
13
  isValidTaskStatus,
@@ -13,10 +16,13 @@ import {
13
16
  minutesForTask,
14
17
  money,
15
18
  parseDuration,
19
+ portfolio,
16
20
  progress,
21
+ projectTabCounts,
17
22
  type Task,
18
23
  type TimeEntry,
19
24
  } from "../lib/work";
25
+ import { SEED_TEAM, shapeSeed } from "../lib/seed";
20
26
 
21
27
  const task = (over: Partial<Task> = {}): Task => ({
22
28
  id: "t1",
@@ -236,3 +242,62 @@ describe("money", () => {
236
242
  expect(money(null)).toBe("$0.00");
237
243
  });
238
244
  });
245
+
246
+ describe("project tabs", () => {
247
+ const projects = [
248
+ { id: "a", status: "active" },
249
+ { id: "b", status: "active" },
250
+ { id: "c", status: "paused" },
251
+ { id: "d", status: "complete" },
252
+ ];
253
+
254
+ test("the sidebar count is the default tab's row count", () => {
255
+ // The sidebar once counted active projects (2) while the list showed all
256
+ // of them (4). Both now read projectTabCounts for the default tab.
257
+ const counts = projectTabCounts(projects);
258
+ expect(counts[DEFAULT_PROJECT_TAB]).toBe(
259
+ projects.filter((p) => inProjectTab(DEFAULT_PROJECT_TAB, p)).length,
260
+ );
261
+ expect(counts[DEFAULT_PROJECT_TAB]).toBe(2);
262
+ });
263
+
264
+ test("each tab counts what it lists", () => {
265
+ const counts = projectTabCounts(projects);
266
+ for (const tab of PROJECT_TABS) {
267
+ expect(counts[tab.id]).toBe(projects.filter((p) => inProjectTab(tab.id, p)).length);
268
+ }
269
+ expect(counts).toEqual({ active: 2, paused: 1, complete: 1, all: 4 });
270
+ });
271
+ });
272
+
273
+ describe("seed", () => {
274
+ test("tasks are spread across the whole team, with some left unassigned", () => {
275
+ const tasks = shapeSeed(Date.parse("2026-07-27T12:00:00Z")).tasks;
276
+ const owners = new Set(tasks.map((t) => t.assignee));
277
+ for (let i = 0; i <= SEED_TEAM.length; i += 1) expect(owners.has(i)).toBe(true);
278
+ expect(owners.has(null)).toBe(true);
279
+ // Only work that hasn't started is unassigned.
280
+ for (const t of tasks) if (t.assignee === null) expect(t.row.status).toBe("todo");
281
+ });
282
+ });
283
+
284
+ describe("portfolio", () => {
285
+ test("totals active projects only and counts the ones over budget", () => {
286
+ const projects = [
287
+ { id: "a", name: "A", status: "active", budgetMinutes: 60, hourlyRateCents: 10_000 },
288
+ { id: "b", name: "B", status: "active", budgetMinutes: 600, hourlyRateCents: 0 },
289
+ { id: "c", name: "C", status: "complete", budgetMinutes: 60, hourlyRateCents: 10_000 },
290
+ ];
291
+ const entries = [
292
+ { id: "1", taskId: "t", projectId: "a", minutes: 90 },
293
+ { id: "2", taskId: "t", projectId: "b", minutes: 30 },
294
+ { id: "3", taskId: "t", projectId: "c", minutes: 500 },
295
+ ];
296
+ expect(portfolio(projects, entries)).toEqual({
297
+ activeCount: 2,
298
+ loggedMinutes: 120,
299
+ billableCents: 15_000,
300
+ overBudgetCount: 1,
301
+ });
302
+ });
303
+ });
@@ -10,3 +10,10 @@ PYLON_OWNER_EMAIL=you@yourrestaurant.com
10
10
  # ── Site URL (optional) ──────────────────────────────────────────────────────
11
11
  # Used by robots.txt + sitemap.xml. Point it at your real domain in production.
12
12
  # SITE_URL=https://yourrestaurant.com
13
+
14
+ # ── Demo data ────────────────────────────────────────────────────────────────
15
+ # `pylon dev` fills an empty reservation book with fictional tables. Seeding
16
+ # runs only under `pylon dev` (it checks PYLON_DEV_MODE and
17
+ # PYLON_DEV_WATCH_DIR), so `pylon start`, Docker, and Pylon Cloud deploys never
18
+ # seed. Set 0 to start empty in development. Never set 1 on a deploy.
19
+ # PYLON_DEMO_DATA=0
@@ -62,7 +62,7 @@ this pass before the user has to ask.
62
62
  - **`serverData` (SSR) is READ-ONLY.** No write methods; the runtime rejects write frames (`SSR_WRITE_FORBIDDEN`). Mutations belong in actions/functions, never in a page render.
63
63
  - **`response.*` / `response.redirect()` / `response.notFound()` must fire in the synchronous shell render**, before any `await` / `<Suspense>`. The HTTP head commits when the shell is ready; status/headers/cookies set from a suspended subtree are lost, and `redirect`/`notFound` thrown below a Suspense boundary are swallowed.
64
64
  - **`ctx.llm`, `ctx.rooms`, and `ctx.connections` are on mutation + action only, NOT query** (reactive purity). `action` has no direct `ctx.db`; use `ctx.runQuery` / `ctx.runMutation`.
65
- - **`ctx.llm.stream(request, onEvent)` streams tokens as they generate** and still resolves with the full response, so `stop_reason === "tool_use"` drives an agent tool loop. Events are `text_delta` / `tool_use_start` / `tool_input_delta` / `done`. Same auth + model-allowlist gating as `ctx.llm.complete`. Streaming does NOT extend the call deadline (`PYLON_FN_CALL_TIMEOUT`, 30s default) — set `timeout: <secs>` on the def for a long run.
65
+ - **`ctx.llm.stream(request, onEvent)` streams tokens as they generate** and still resolves with the full response, so `stop_reason === "tool_use"` drives an agent tool loop. Events are `text_delta` / `tool_use_start` / `tool_input_delta` / `done`. Same auth + model-allowlist gating as `ctx.llm.complete`. The call timeout is an idle timeout (`PYLON_FN_CALL_TIMEOUT`, 30s default; Stack0 Cloud 300s): each stream event resets it, and the total run is capped at 10× the timeout. Set `timeout: <secs>` on the def when a step (a slow tool call) can be silent longer than that.
66
66
  - **`ctx.stream.write(text)` streams to the caller — and every fn stream is RESUMABLE**: the server buffers frames under a stream id (`streamFn`'s `onStreamId`), so a dropped connection or closed tab catches up via `resumeStream(id)`, including the final result after the handler finished. `ctx.rooms.broadcast(room, topic, data)` fans out to every CURRENTLY-CONNECTED subscriber (second device, second tab) but does not replay missed messages — use the stream id for anything that must survive a gap. Broadcast resolves `{ delivered: false }` when the room has no members — a no-op, not an error. Clients receive with `useRoomMessages(room, cb)` from `@pylonsync/react`; `useRoom` is the send side.
67
67
  - **It's `db.useQueryOne`, not `useOne`.** Validators and field types have aliases: `v.bool`/`v.boolean`, `v.float`/`v.number`.
68
68
  - **Use the supported file and scheduling APIs.** Files go through `<FileUpload>` and `/api/files/*`; an upload is readable by its uploader only, so pass `visibility: "public"` to `uploadFile` or `<FileUpload>` for files every visitor sees; there is no `ctx.files`. One-shot work uses `ctx.scheduler.runAfter`, `runAt`, or `cancel`; there is no `defineWorkflow` or `defineJob`. Recurring work uses `cron("0 * * * *", "fnName")` in `buildManifest({ crons: [...] })`, imported from `@pylonsync/sdk`. Make the target function `internal: true`. It runs with anonymous auth, but its own `ctx.db.*` calls are server-side and bypass policies. Use `ctx.auth.elevate({ admin: true, reason: "..." })`, with a mandatory reason, only when chaining another internal function through `ctx.scheduler`.
@@ -17,7 +17,7 @@ time in one tab and watch it change to "Full" in another without refreshing.
17
17
 
18
18
  ## How the realtime works
19
19
 
20
- - `app/reservation-widget.tsx` subscribes to the public, PII-free
20
+ - `app/(marketing)/reservation-widget.tsx` subscribes to the public, PII-free
21
21
  `ReservationSlot` markers with `db.useQuery` and COUNTS them per seating, so
22
22
  "tables left" ticks down live across every tab.
23
23
  - `functions/createReservation.ts` is a public **mutation** that re-checks the
@@ -43,25 +43,51 @@ details, notes — with confirm/cancel, plus live covers + counts.
43
43
  Set `PYLON_OWNER_EMAIL` in `.env` (see `.env.example`) to the email you'll sign
44
44
  in with, then create that account at `/login`.
45
45
 
46
+ ## Demo data
47
+
48
+ In `pylon dev`, an empty reservation book gets fictional tables from the past
49
+ week and the next two weeks (`functions/seedDemo.ts`, `lib/demo.ts`). Each one
50
+ writes a `Reservation` and its `ReservationSlot` marker, like a real
51
+ reservation, so the public calendar shows fewer tables left and the dashboard
52
+ lists the same guests. Seeding runs only when `PYLON_DEMO_DATA` is on, or when
53
+ it is unset and the app runs under `pylon dev`, and only while the `Reservation` table
54
+ is empty. `pylon start`, Docker, and Pylon Cloud deploys do not seed, because only `pylon dev` sets `PYLON_DEV_WATCH_DIR`. Set
55
+ `PYLON_DEMO_DATA=0` to start empty in development. Delete both files once you
56
+ take real reservations.
57
+
58
+ ## Map or hours card
59
+
60
+ Set `location.mapEmbedUrl` in `lib/site.config.ts` to a Google Maps embed URL to
61
+ show a map beside the address. Without one, the site shows the week's service
62
+ hours (from `reservations.hours`, the same hours the calendar uses) and a
63
+ directions link. The dashboard reminds you while the URL is empty.
64
+
46
65
  ## Rebrand + reconfigure it
47
66
 
48
67
  Brand, colors, the menu, seating hours, tables per seating, lead time, reviews,
49
68
  location, and FAQ content live in **`lib/site.config.ts`**. Editing or
50
69
  generating that file updates both the site and reservation engine.
51
70
 
71
+ The tab icon is `app/icon.svg` (with `public/favicon.ico` for browsers that
72
+ request `/favicon.ico`). Both carry this brand's letter and colors; replace
73
+ them when you rename the brand.
74
+
52
75
  ## Layout
53
76
 
54
77
  ```
55
78
  app.ts Reservation + ReservationSlot + User + policies
56
79
  lib/site.config.ts ALL copy + brand + menu + seating config
57
80
  lib/slots.ts pure seating math, shared by picker + server
81
+ lib/hours.ts weekly hours rows + directions link
82
+ lib/demo.ts demo reservations + the dev-only gate
83
+ functions/seedDemo.ts dev-only demo reservations (see Demo data)
58
84
  lib/reservation.ts shared reservation-row types
59
85
  lib/owner.ts owner-email gate (PYLON_OWNER_EMAIL)
60
86
  functions/createReservation.ts public mutation: capacity re-check + reserve
61
87
  functions/reservationsForOwner.ts owner-only query: reservations + guest PII
62
88
  functions/{confirm,cancel}Reservation.ts owner-only mutations
63
- app/page.tsx landing (hero + menu + reviews + location)
64
- app/reservation-widget.tsx client island: live table picker + form
89
+ app/(marketing)/page.tsx landing (hero + menu + reviews + location)
90
+ app/(marketing)/reservation-widget.tsx client island: live table picker + form
65
91
  app/dashboard/ owner dashboard (auth-gated, live)
66
92
  ```
67
93
 
@@ -22,7 +22,9 @@ export default function MarketingLayout({ children, auth }: LayoutProps) {
22
22
  const { brand } = siteConfig;
23
23
 
24
24
  return (
25
- <>
25
+ // The ink background sits behind the translucent nav, so the nav reads as
26
+ // dark glass over the page instead of grey over the white body.
27
+ <div className="flex min-h-screen flex-col bg-[var(--ink)]">
26
28
  {/* The nav sits over the hero photo: dark, translucent, cream type. */}
27
29
  <header className="sticky top-0 z-30 border-b border-white/10 bg-[var(--ink)]/80 text-[var(--cream)] backdrop-blur">
28
30
  <div className="mx-auto flex h-16 max-w-6xl items-center justify-between px-6">
@@ -57,7 +59,7 @@ export default function MarketingLayout({ children, auth }: LayoutProps) {
57
59
  <main className="flex-1">{children}</main>
58
60
 
59
61
  <SiteFooter />
60
- </>
62
+ </div>
61
63
  );
62
64
  }
63
65
 
@@ -3,6 +3,7 @@ import { type Metadata } from "@pylonsync/react";
3
3
  import { LiveBadge } from "@/components/marketing";
4
4
  import { ReservationWidget } from "./reservation-widget";
5
5
  import { siteConfig, type DayHours } from "@/lib/site.config";
6
+ import { clock, directionsUrl, hoursRows } from "@/lib/hours";
6
7
 
7
8
  export const metadata: Metadata = {
8
9
  title: siteConfig.seo.title,
@@ -24,7 +25,8 @@ export default function LandingPage() {
24
25
  return (
25
26
  <div className="bg-[var(--ink)] text-[var(--cream)]">
26
27
  {/* HERO: the room, full bleed, the words on top. */}
27
- <section className="relative isolate min-h-[88vh] overflow-hidden">
28
+ {/* -mt-16 runs the photo up under the translucent nav. */}
29
+ <section className="relative isolate -mt-16 min-h-[88vh] overflow-hidden">
28
30
  <img src={hero.image} alt="" className="absolute inset-0 -z-20 size-full object-cover" />
29
31
  <div className="absolute inset-0 -z-10 bg-gradient-to-t from-[var(--ink)] via-[var(--ink)]/60 to-[var(--ink)]/20" />
30
32
  <div className={`${WRAP} flex min-h-[88vh] flex-col justify-end pb-16 pt-32`}>
@@ -155,29 +157,34 @@ export default function LandingPage() {
155
157
 
156
158
  {/* VISIT */}
157
159
  <section id="visit" className="border-t border-white/10">
158
- <div className={`${WRAP} grid gap-10 py-16 lg:grid-cols-2`}>
159
- <div>
160
+ <div className={`${WRAP} grid grid-cols-[minmax(0,1fr)] gap-10 py-16 lg:grid-cols-2`}>
161
+ <div className="min-w-0">
160
162
  <h2 className="font-display text-balance text-[2.25rem] leading-[1.08] sm:text-[3rem]">{location.headline}</h2>
161
- <div className="mt-6 space-y-2 text-[15px] leading-relaxed text-[var(--cream-2)]">
162
- <p className="text-[var(--cream)]">{location.address}</p>
163
- <p>{location.hoursText}</p>
164
- <p>
165
- <a href={`tel:${location.phone}`} className="text-[var(--cream)] underline-offset-4 hover:underline">
166
- {location.phone}
167
- </a>{" "}
168
- · <a href={`mailto:${location.email}`} className="underline-offset-4 hover:underline">{location.email}</a>
169
- </p>
163
+ <p className="font-display mt-6 max-w-sm text-[1.5rem] leading-snug sm:text-[1.75rem]">{location.address}</p>
164
+ <div className="mt-8 flex flex-wrap items-center gap-3">
165
+ <a
166
+ href={directionsUrl(location.address)}
167
+ target="_blank"
168
+ rel="noreferrer"
169
+ className="inline-flex items-center rounded-full bg-brand px-5 py-2.5 text-[14px] font-medium text-white transition-opacity hover:opacity-90"
170
+ >
171
+ Get directions
172
+ </a>
173
+ <a href={`tel:${location.phone}`} className="inline-flex items-center rounded-full border border-white/25 px-5 py-2.5 text-[14px] font-medium transition-colors hover:bg-white/10">
174
+ Call {location.phone}
175
+ </a>
170
176
  </div>
171
- <a href="#reserve" className="mt-8 inline-flex items-center rounded-full border border-white/25 px-5 py-2.5 text-[14px] font-medium transition-colors hover:bg-white/10">
172
- {hero.ctaLabel}
173
- </a>
177
+ <p className="mt-6 text-[14px] text-[var(--cream-2)]">
178
+ Or write to{" "}
179
+ <a href={`mailto:${location.email}`} className="text-[var(--cream)] underline-offset-4 hover:underline">
180
+ {location.email}
181
+ </a>
182
+ </p>
174
183
  </div>
175
184
  {location.mapEmbedUrl ? (
176
- <iframe title="Map" src={location.mapEmbedUrl} className="h-72 w-full rounded-[6px] border border-white/10 grayscale" loading="lazy" />
185
+ <iframe title={`Map to ${siteConfig.brand.name}`} src={location.mapEmbedUrl} className="h-80 w-full rounded-[6px] border border-white/10 grayscale" loading="lazy" />
177
186
  ) : (
178
- <div className="grid h-72 place-items-center rounded-[6px] border border-white/10 bg-[var(--ink-2)] text-[13px] text-[var(--cream-2)]">
179
- Set <code className="mx-1 font-mono">location.mapEmbedUrl</code> to show a map here
180
- </div>
187
+ <HoursCard />
181
188
  )}
182
189
  </div>
183
190
  </section>
@@ -210,8 +217,25 @@ function hoursLabel(h: DayHours): string {
210
217
  return `${clock(h.open)} – ${clock(h.close)}`;
211
218
  }
212
219
 
213
- function clock(t: string): string {
214
- const [hh, mm] = t.split(":").map(Number);
215
- const h12 = ((hh + 11) % 12) + 1;
216
- return `${h12}${mm ? `:${String(mm).padStart(2, "0")}` : ""}${hh < 12 ? "am" : "pm"}`;
220
+ // The week's service hours on a cream card, from the same config the
221
+ // reservation calendar reads. Shown in place of a map when
222
+ // `location.mapEmbedUrl` is empty.
223
+ function HoursCard() {
224
+ const rows = hoursRows(siteConfig.reservations.hours);
225
+ return (
226
+ <div className="rounded-[6px] bg-[var(--cream)] p-6 text-[var(--ink)] sm:p-8">
227
+ <h3 className="font-display text-[1.75rem] italic leading-none">Dinner service</h3>
228
+ <dl className="mt-6 divide-y divide-[var(--ink)]/10">
229
+ {rows.map((r) => (
230
+ <div key={r.days} className="flex items-baseline justify-between gap-4 py-3">
231
+ <dt className="text-[15px] font-medium">{r.days}</dt>
232
+ <dd className={"text-[15px] tabular-nums " + (r.closed ? "text-[var(--ink)]/45" : "")}>{r.time}</dd>
233
+ </div>
234
+ ))}
235
+ </dl>
236
+ <a href="#reserve" className="mt-6 inline-flex items-center text-[14px] font-medium text-brand underline-offset-4 hover:underline">
237
+ {siteConfig.hero.ctaLabel}
238
+ </a>
239
+ </div>
240
+ );
217
241
  }
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
 
3
- import React, { useMemo, useState } from "react";
3
+ import React, { useEffect, useMemo, useRef, useState } from "react";
4
4
  import { db, callFn } from "@pylonsync/react";
5
5
  import { EnsureGuest } from "@pylonsync/client";
6
6
  import { siteConfig } from "@/lib/site.config";
@@ -33,13 +33,38 @@ export function ReservationWidget() {
33
33
  function Picker() {
34
34
  const cfg = siteConfig.reservations;
35
35
  const { data: markers, loading } = db.useQuery<SlotMarker>("ReservationSlot");
36
+
37
+ // In `pylon dev` an empty book gets demo reservations (functions/seedDemo.ts),
38
+ // which show up here as fewer tables left. The call goes out only when no
39
+ // table is reserved, so a live site with reservations never makes it.
40
+ const empty = !loading && markers.length === 0;
41
+ const seedSent = useRef(false);
42
+ useEffect(() => {
43
+ if (!empty || seedSent.current) return;
44
+ seedSent.current = true;
45
+ void callFn("seedDemo", {}).catch(() => {});
46
+ }, [empty]);
36
47
  const [nowMs] = useState(() => Date.now());
37
48
 
38
49
  const openDays = useMemo(() => {
39
50
  const days: string[] = [];
40
51
  for (let i = 0; i < cfg.daysAhead; i++) {
41
52
  const key = localDateKey(i, nowMs);
42
- if (cfg.hours[weekdayOf(key)]) days.push(key);
53
+ const hrs = cfg.hours[weekdayOf(key)];
54
+ if (!hrs) continue;
55
+ // Today drops out once its last seating is too soon to book.
56
+ if (i === 0) {
57
+ const left = seatingsForDay({
58
+ dayISODate: key,
59
+ open: hrs.open,
60
+ close: hrs.close,
61
+ slotMinutes: cfg.slotMinutes,
62
+ leadTimeHours: cfg.leadTimeHours,
63
+ nowMs,
64
+ }).some((s) => !s.past);
65
+ if (!left) continue;
66
+ }
67
+ days.push(key);
43
68
  }
44
69
  return days;
45
70
  }, [cfg.daysAhead, cfg.hours, nowMs]);
@@ -68,10 +93,13 @@ function Picker() {
68
93
  slotMinutes: cfg.slotMinutes,
69
94
  leadTimeHours: cfg.leadTimeHours,
70
95
  nowMs,
71
- }).map((s) => {
72
- const remaining = cfg.tablesPerSlot - (takenByTime.get(s.startsAt) ?? 0);
73
- return { startsAt: s.startsAt, remaining, available: !s.past && remaining > 0 };
74
- });
96
+ })
97
+ // Seatings too soon to book are left out, so "Full" only ever means full.
98
+ .filter((s) => !s.past)
99
+ .map((s) => {
100
+ const remaining = Math.max(0, cfg.tablesPerSlot - (takenByTime.get(s.startsAt) ?? 0));
101
+ return { startsAt: s.startsAt, remaining, available: remaining > 0 };
102
+ });
75
103
  }, [day, cfg, takenByTime, nowMs]);
76
104
 
77
105
  // Clear an in-progress selection if its seating just filled up (live).
@@ -142,8 +170,10 @@ function Picker() {
142
170
  <div className="mt-5 border-t border-zinc-100 pt-5">
143
171
  {loading ? (
144
172
  <SeatingsSkeleton />
173
+ ) : !cfg.hours[weekdayOf(day)] ? (
174
+ <p className="py-6 text-center text-sm text-zinc-500">Closed that day. Pick another.</p>
145
175
  ) : seatings.length === 0 ? (
146
- <p className="py-6 text-center text-sm text-zinc-500">Closed that day — pick another.</p>
176
+ <p className="py-6 text-center text-sm text-zinc-500">No seatings left that day. Pick another.</p>
147
177
  ) : (
148
178
  <div className="grid grid-cols-3 gap-2 sm:grid-cols-4">
149
179
  {seatings.map((seat) => {
@@ -51,7 +51,7 @@ export function AuthForm() {
51
51
  <div className="space-y-5">
52
52
  <form onSubmit={onSubmit} className="space-y-4">
53
53
  <label className="block">
54
- <span className="mb-1.5 block text-[13px] font-medium text-zinc-700">Email</span>
54
+ <span className="mb-1.5 block text-[13px] font-medium text-[var(--cream-2)]">Email</span>
55
55
  <input
56
56
  type="email"
57
57
  value={email}
@@ -59,11 +59,11 @@ export function AuthForm() {
59
59
  required
60
60
  autoComplete="email"
61
61
  placeholder="you@yourbusiness.com"
62
- className="h-10 w-full rounded-lg border border-zinc-300 bg-white px-3 text-sm text-zinc-900 outline-none transition placeholder:text-zinc-400 focus:border-brand focus:ring-2 focus:ring-brand/20"
62
+ className="h-11 w-full rounded-[6px] border border-white/15 bg-[var(--ink)] px-3.5 text-[15px] text-[var(--cream)] outline-none transition placeholder:text-[var(--cream-2)]/60 focus:border-brand"
63
63
  />
64
64
  </label>
65
65
  <label className="block">
66
- <span className="mb-1.5 block text-[13px] font-medium text-zinc-700">Password</span>
66
+ <span className="mb-1.5 block text-[13px] font-medium text-[var(--cream-2)]">Password</span>
67
67
  <input
68
68
  type="password"
69
69
  value={password}
@@ -71,24 +71,24 @@ export function AuthForm() {
71
71
  required
72
72
  autoComplete={mode === "login" ? "current-password" : "new-password"}
73
73
  placeholder={mode === "login" ? "Your password" : "At least 10 characters"}
74
- className="h-10 w-full rounded-lg border border-zinc-300 bg-white px-3 text-sm text-zinc-900 outline-none transition placeholder:text-zinc-400 focus:border-brand focus:ring-2 focus:ring-brand/20"
74
+ className="h-11 w-full rounded-[6px] border border-white/15 bg-[var(--ink)] px-3.5 text-[15px] text-[var(--cream)] outline-none transition placeholder:text-[var(--cream-2)]/60 focus:border-brand"
75
75
  />
76
76
  </label>
77
77
  {error ? (
78
- <p className="rounded-lg border border-red-200 bg-red-50 px-3 py-2 text-[13px] leading-snug text-red-700">
78
+ <p className="rounded-[6px] border border-brand/40 bg-brand/10 px-3 py-2 text-[13px] leading-snug text-[var(--cream)]">
79
79
  {error}
80
80
  </p>
81
81
  ) : null}
82
82
  <button
83
83
  type="submit"
84
84
  disabled={pending}
85
- className="inline-flex h-10 w-full items-center justify-center rounded-lg bg-zinc-900 text-sm font-medium text-white transition-colors hover:bg-zinc-700 disabled:opacity-60"
85
+ className="inline-flex h-11 w-full items-center justify-center rounded-full bg-brand text-[14px] font-medium text-white transition-opacity hover:opacity-90 disabled:opacity-60"
86
86
  >
87
87
  {pending ? "…" : mode === "login" ? "Sign in" : "Create account"}
88
88
  </button>
89
89
  </form>
90
90
 
91
- <p className="text-center text-[13px] text-zinc-500">
91
+ <p className="text-center text-[13px] text-[var(--cream-2)]">
92
92
  {mode === "login" ? "First time here?" : "Already have an account?"}{" "}
93
93
  <button
94
94
  type="button"
@@ -96,7 +96,7 @@ export function AuthForm() {
96
96
  setMode(mode === "login" ? "signup" : "login");
97
97
  setError(null);
98
98
  }}
99
- className="font-medium text-zinc-900 underline underline-offset-2"
99
+ className="font-medium text-[var(--cream)] underline underline-offset-4"
100
100
  >
101
101
  {mode === "login" ? "Create the owner account" : "Sign in"}
102
102
  </button>