@pylonsync/create-pylon 0.3.274 → 0.3.276
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.
- package/bin/create-pylon.js +80 -0
- package/package.json +1 -1
- package/templates/ARCHETYPES.md +339 -0
- package/templates/agency/.env.example +12 -0
- package/templates/agency/AGENTS.md +61 -0
- package/templates/agency/README.md +90 -0
- package/templates/agency/app/auth-form.tsx +129 -0
- package/templates/agency/app/contact-form.tsx +258 -0
- package/templates/agency/app/dashboard/dashboard-client.tsx +1440 -0
- package/templates/agency/app/dashboard/page.tsx +70 -0
- package/templates/agency/app/error.tsx +26 -0
- package/templates/agency/app/globals.css +148 -0
- package/templates/agency/app/layout.tsx +174 -0
- package/templates/agency/app/login/page.tsx +39 -0
- package/templates/agency/app/not-found.tsx +19 -0
- package/templates/agency/app/page.tsx +249 -0
- package/templates/agency/app/robots.ts +12 -0
- package/templates/agency/app/seeder.tsx +26 -0
- package/templates/agency/app/sitemap.ts +9 -0
- package/templates/agency/app/work/[slug]/page.tsx +182 -0
- package/templates/agency/app/work/page.tsx +83 -0
- package/templates/agency/app.ts +284 -0
- package/templates/agency/components/marketing.tsx +187 -0
- package/templates/agency/components/section-scroller.tsx +35 -0
- package/templates/agency/components/ui/button.tsx +56 -0
- package/templates/agency/components/ui/card.tsx +90 -0
- package/templates/agency/components.json +20 -0
- package/templates/agency/functions/bookInquiry.ts +42 -0
- package/templates/agency/functions/clientsForOwner.ts +27 -0
- package/templates/agency/functions/declineInquiry.ts +41 -0
- package/templates/agency/functions/deleteClient.ts +27 -0
- package/templates/agency/functions/deleteInvoice.ts +19 -0
- package/templates/agency/functions/deleteProject.ts +20 -0
- package/templates/agency/functions/inquiriesForOwner.ts +31 -0
- package/templates/agency/functions/invoicesForOwner.ts +27 -0
- package/templates/agency/functions/seedCapacity.ts +26 -0
- package/templates/agency/functions/seedProjects.ts +41 -0
- package/templates/agency/functions/seedStudioBackoffice.ts +74 -0
- package/templates/agency/functions/setCapacity.ts +32 -0
- package/templates/agency/functions/setInvoiceStatus.ts +27 -0
- package/templates/agency/functions/setProjectFlags.ts +35 -0
- package/templates/agency/functions/submitInquiry.ts +55 -0
- package/templates/agency/functions/upsertClient.ts +73 -0
- package/templates/agency/functions/upsertInvoice.ts +113 -0
- package/templates/agency/functions/upsertProject.ts +97 -0
- package/templates/agency/gitignore +10 -0
- package/templates/agency/lib/agency.ts +189 -0
- package/templates/agency/lib/invoice-pdf.tsx +174 -0
- package/templates/agency/lib/owner.ts +26 -0
- package/templates/agency/lib/site.config.ts +418 -0
- package/templates/agency/lib/utils.ts +10 -0
- package/templates/agency/package.json +35 -0
- package/templates/agency/tsconfig.json +18 -0
- package/templates/ai-chat/.env.example +33 -0
- package/templates/ai-chat/AGENTS.md +61 -0
- package/templates/ai-chat/README.md +99 -0
- package/templates/ai-chat/app/auth-form.tsx +124 -0
- package/templates/ai-chat/app/chat-client.tsx +727 -0
- package/templates/ai-chat/app/error.tsx +26 -0
- package/templates/ai-chat/app/globals.css +148 -0
- package/templates/ai-chat/app/layout.tsx +75 -0
- package/templates/ai-chat/app/login/page.tsx +39 -0
- package/templates/ai-chat/app/not-found.tsx +19 -0
- package/templates/ai-chat/app/page.tsx +23 -0
- package/templates/ai-chat/app.ts +121 -0
- package/templates/ai-chat/components.json +20 -0
- package/templates/ai-chat/functions/deleteConversation.ts +33 -0
- package/templates/ai-chat/gitignore +10 -0
- package/templates/ai-chat/lib/site.config.ts +103 -0
- package/templates/ai-chat/lib/utils.ts +10 -0
- package/templates/ai-chat/package.json +34 -0
- package/templates/ai-chat/tsconfig.json +18 -0
- package/templates/ai-studio/.env.example +19 -0
- package/templates/ai-studio/AGENTS.md +61 -0
- package/templates/ai-studio/README.md +83 -0
- package/templates/ai-studio/app/auth-form.tsx +124 -0
- package/templates/ai-studio/app/error.tsx +26 -0
- package/templates/ai-studio/app/globals.css +148 -0
- package/templates/ai-studio/app/layout.tsx +75 -0
- package/templates/ai-studio/app/login/page.tsx +39 -0
- package/templates/ai-studio/app/not-found.tsx +19 -0
- package/templates/ai-studio/app/page.tsx +34 -0
- package/templates/ai-studio/app/studio-client.tsx +357 -0
- package/templates/ai-studio/app.ts +108 -0
- package/templates/ai-studio/components.json +20 -0
- package/templates/ai-studio/functions/_getGeneration.ts +25 -0
- package/templates/ai-studio/functions/_updateGeneration.ts +37 -0
- package/templates/ai-studio/functions/generate.ts +42 -0
- package/templates/ai-studio/functions/pollGeneration.ts +134 -0
- package/templates/ai-studio/gitignore +10 -0
- package/templates/ai-studio/lib/site.config.ts +80 -0
- package/templates/ai-studio/lib/studio.ts +52 -0
- package/templates/ai-studio/lib/utils.ts +10 -0
- package/templates/ai-studio/package.json +34 -0
- package/templates/ai-studio/tsconfig.json +18 -0
- package/templates/creator/.env.example +12 -0
- package/templates/creator/AGENTS.md +61 -0
- package/templates/creator/README.md +67 -0
- package/templates/creator/app/auth-form.tsx +129 -0
- package/templates/creator/app/dashboard/dashboard-client.tsx +297 -0
- package/templates/creator/app/dashboard/page.tsx +70 -0
- package/templates/creator/app/error.tsx +26 -0
- package/templates/creator/app/globals.css +148 -0
- package/templates/creator/app/layout.tsx +160 -0
- package/templates/creator/app/login/page.tsx +39 -0
- package/templates/creator/app/newsletter-signup.tsx +162 -0
- package/templates/creator/app/not-found.tsx +19 -0
- package/templates/creator/app/page.tsx +160 -0
- package/templates/creator/app/robots.ts +12 -0
- package/templates/creator/app/sitemap.ts +9 -0
- package/templates/creator/app.ts +134 -0
- package/templates/creator/components/marketing.tsx +148 -0
- package/templates/creator/components/section-scroller.tsx +35 -0
- package/templates/creator/components/ui/button.tsx +56 -0
- package/templates/creator/components/ui/card.tsx +90 -0
- package/templates/creator/components.json +20 -0
- package/templates/creator/functions/subscribe.ts +82 -0
- package/templates/creator/functions/subscriberStats.ts +75 -0
- package/templates/creator/gitignore +10 -0
- package/templates/creator/lib/owner.ts +26 -0
- package/templates/creator/lib/site.config.ts +173 -0
- package/templates/creator/lib/stats.ts +30 -0
- package/templates/creator/lib/utils.ts +10 -0
- package/templates/creator/package.json +34 -0
- package/templates/creator/tsconfig.json +18 -0
- package/templates/default/app/layout.tsx +26 -27
- package/templates/default/app/page.tsx +90 -274
- package/templates/default/lib/products.ts +9 -122
- package/templates/default/lib/site.config.ts +739 -0
- package/templates/default/lib/site.ts +14 -261
- package/templates/directory/.env.example +12 -0
- package/templates/directory/AGENTS.md +61 -0
- package/templates/directory/README.md +80 -0
- package/templates/directory/app/auth-form.tsx +129 -0
- package/templates/directory/app/dashboard/dashboard-client.tsx +205 -0
- package/templates/directory/app/dashboard/page.tsx +70 -0
- package/templates/directory/app/directory-browse.tsx +328 -0
- package/templates/directory/app/error.tsx +26 -0
- package/templates/directory/app/globals.css +148 -0
- package/templates/directory/app/layout.tsx +171 -0
- package/templates/directory/app/login/page.tsx +39 -0
- package/templates/directory/app/not-found.tsx +19 -0
- package/templates/directory/app/page.tsx +50 -0
- package/templates/directory/app/robots.ts +12 -0
- package/templates/directory/app/sitemap.ts +9 -0
- package/templates/directory/app/submit/page.tsx +30 -0
- package/templates/directory/app/submit-form.tsx +151 -0
- package/templates/directory/app.ts +146 -0
- package/templates/directory/components/marketing.tsx +148 -0
- package/templates/directory/components/section-scroller.tsx +35 -0
- package/templates/directory/components/ui/button.tsx +56 -0
- package/templates/directory/components/ui/card.tsx +90 -0
- package/templates/directory/components.json +20 -0
- package/templates/directory/functions/approveSubmission.ts +45 -0
- package/templates/directory/functions/rejectSubmission.ts +20 -0
- package/templates/directory/functions/seedListings.ts +33 -0
- package/templates/directory/functions/submissionsForOwner.ts +29 -0
- package/templates/directory/functions/submitListing.ts +63 -0
- package/templates/directory/functions/upvote.ts +24 -0
- package/templates/directory/gitignore +10 -0
- package/templates/directory/lib/directory.ts +45 -0
- package/templates/directory/lib/owner.ts +26 -0
- package/templates/directory/lib/site.config.ts +130 -0
- package/templates/directory/lib/utils.ts +10 -0
- package/templates/directory/package.json +34 -0
- package/templates/directory/tsconfig.json +18 -0
- package/templates/local-service/.env.example +12 -0
- package/templates/local-service/AGENTS.md +61 -0
- package/templates/local-service/README.md +82 -0
- package/templates/local-service/app/auth-form.tsx +129 -0
- package/templates/local-service/app/booking-widget.tsx +399 -0
- package/templates/local-service/app/dashboard/dashboard-client.tsx +304 -0
- package/templates/local-service/app/dashboard/page.tsx +63 -0
- package/templates/local-service/app/error.tsx +26 -0
- package/templates/local-service/app/globals.css +148 -0
- package/templates/local-service/app/layout.tsx +151 -0
- package/templates/local-service/app/login/page.tsx +39 -0
- package/templates/local-service/app/not-found.tsx +19 -0
- package/templates/local-service/app/page.tsx +233 -0
- package/templates/local-service/app/robots.ts +12 -0
- package/templates/local-service/app/sitemap.ts +9 -0
- package/templates/local-service/app.ts +131 -0
- package/templates/local-service/components/marketing.tsx +162 -0
- package/templates/local-service/components/section-scroller.tsx +35 -0
- package/templates/local-service/components/ui/button.tsx +56 -0
- package/templates/local-service/components/ui/card.tsx +90 -0
- package/templates/local-service/components.json +20 -0
- package/templates/local-service/functions/bookingsForOwner.ts +30 -0
- package/templates/local-service/functions/cancelBooking.ts +27 -0
- package/templates/local-service/functions/confirmBooking.ts +18 -0
- package/templates/local-service/functions/createBooking.ts +98 -0
- package/templates/local-service/gitignore +10 -0
- package/templates/local-service/lib/booking.ts +24 -0
- package/templates/local-service/lib/owner.ts +26 -0
- package/templates/local-service/lib/site.config.ts +232 -0
- package/templates/local-service/lib/slots.ts +97 -0
- package/templates/local-service/lib/utils.ts +10 -0
- package/templates/local-service/package.json +34 -0
- package/templates/local-service/tsconfig.json +18 -0
- package/templates/marketplace/.env.example +9 -0
- package/templates/marketplace/AGENTS.md +61 -0
- package/templates/marketplace/README.md +78 -0
- package/templates/marketplace/app/_components/CategoryIcon.tsx +40 -0
- package/templates/marketplace/app/error.tsx +26 -0
- package/templates/marketplace/app/globals.css +64 -0
- package/templates/marketplace/app/layout.tsx +60 -0
- package/templates/marketplace/app/listing/[id]/page.tsx +163 -0
- package/templates/marketplace/app/me/page.tsx +15 -0
- package/templates/marketplace/app/not-found.tsx +20 -0
- package/templates/marketplace/app/page.tsx +159 -0
- package/templates/marketplace/app/robots.ts +12 -0
- package/templates/marketplace/app/sell/page.tsx +26 -0
- package/templates/marketplace/app/sitemap.ts +14 -0
- package/templates/marketplace/app.ts +190 -0
- package/templates/marketplace/client/AuthNav.tsx +46 -0
- package/templates/marketplace/client/LiveTicker.tsx +104 -0
- package/templates/marketplace/client/LoginCard.tsx +130 -0
- package/templates/marketplace/client/MarketProvider.tsx +148 -0
- package/templates/marketplace/client/MyMarket.tsx +180 -0
- package/templates/marketplace/client/OfferPanel.tsx +355 -0
- package/templates/marketplace/client/SeedOnEmpty.tsx +26 -0
- package/templates/marketplace/client/SellForm.tsx +160 -0
- package/templates/marketplace/client/WatchButton.tsx +88 -0
- package/templates/marketplace/client/market.ts +341 -0
- package/templates/marketplace/functions/buyNow.ts +78 -0
- package/templates/marketplace/functions/makeOffer.ts +65 -0
- package/templates/marketplace/functions/respondToOffer.ts +62 -0
- package/templates/marketplace/functions/seedMarket.ts +90 -0
- package/templates/marketplace/gitignore +10 -0
- package/templates/marketplace/package.json +35 -0
- package/templates/marketplace/tsconfig.json +14 -0
- package/templates/marketplace/ui/badge.tsx +30 -0
- package/templates/marketplace/ui/button.tsx +49 -0
- package/templates/marketplace/ui/card.tsx +48 -0
- package/templates/marketplace/ui/input.tsx +17 -0
- package/templates/marketplace/ui/label.tsx +18 -0
- package/templates/marketplace/ui/textarea.tsx +17 -0
- package/templates/marketplace/ui/tokens.css +32 -0
- package/templates/marketplace/ui/utils.ts +6 -0
- package/templates/restaurant/.env.example +12 -0
- package/templates/restaurant/AGENTS.md +61 -0
- package/templates/restaurant/README.md +77 -0
- package/templates/restaurant/app/auth-form.tsx +129 -0
- package/templates/restaurant/app/dashboard/dashboard-client.tsx +263 -0
- package/templates/restaurant/app/dashboard/page.tsx +59 -0
- package/templates/restaurant/app/error.tsx +26 -0
- package/templates/restaurant/app/globals.css +148 -0
- package/templates/restaurant/app/layout.tsx +151 -0
- package/templates/restaurant/app/login/page.tsx +39 -0
- package/templates/restaurant/app/not-found.tsx +19 -0
- package/templates/restaurant/app/page.tsx +194 -0
- package/templates/restaurant/app/reservation-widget.tsx +359 -0
- package/templates/restaurant/app/robots.ts +12 -0
- package/templates/restaurant/app/sitemap.ts +9 -0
- package/templates/restaurant/app.ts +115 -0
- package/templates/restaurant/components/marketing.tsx +162 -0
- package/templates/restaurant/components/section-scroller.tsx +35 -0
- package/templates/restaurant/components/ui/button.tsx +56 -0
- package/templates/restaurant/components/ui/card.tsx +90 -0
- package/templates/restaurant/components.json +20 -0
- package/templates/restaurant/functions/cancelReservation.ts +26 -0
- package/templates/restaurant/functions/confirmReservation.ts +17 -0
- package/templates/restaurant/functions/createReservation.ts +92 -0
- package/templates/restaurant/functions/reservationsForOwner.ts +28 -0
- package/templates/restaurant/gitignore +10 -0
- package/templates/restaurant/lib/owner.ts +26 -0
- package/templates/restaurant/lib/reservation.ts +22 -0
- package/templates/restaurant/lib/site.config.ts +218 -0
- package/templates/restaurant/lib/slots.ts +55 -0
- package/templates/restaurant/lib/utils.ts +10 -0
- package/templates/restaurant/package.json +34 -0
- package/templates/restaurant/tsconfig.json +18 -0
- package/templates/shop/.env.example +32 -0
- package/templates/shop/AGENTS.md +61 -0
- package/templates/shop/README.md +102 -0
- package/templates/shop/app/auth-form.tsx +129 -0
- package/templates/shop/app/dashboard/dashboard-client.tsx +264 -0
- package/templates/shop/app/dashboard/page.tsx +59 -0
- package/templates/shop/app/error.tsx +26 -0
- package/templates/shop/app/globals.css +148 -0
- package/templates/shop/app/layout.tsx +160 -0
- package/templates/shop/app/login/page.tsx +39 -0
- package/templates/shop/app/not-found.tsx +19 -0
- package/templates/shop/app/page.tsx +95 -0
- package/templates/shop/app/robots.ts +12 -0
- package/templates/shop/app/shop-client.tsx +436 -0
- package/templates/shop/app/sitemap.ts +9 -0
- package/templates/shop/app/success/page.tsx +33 -0
- package/templates/shop/app.ts +134 -0
- package/templates/shop/components/marketing.tsx +96 -0
- package/templates/shop/components/section-scroller.tsx +35 -0
- package/templates/shop/components/ui/button.tsx +56 -0
- package/templates/shop/components/ui/card.tsx +90 -0
- package/templates/shop/components.json +20 -0
- package/templates/shop/functions/cancelOrder.ts +33 -0
- package/templates/shop/functions/checkout.ts +130 -0
- package/templates/shop/functions/fulfillOrder.ts +17 -0
- package/templates/shop/functions/markGroupPaid.ts +26 -0
- package/templates/shop/functions/ordersForOwner.ts +28 -0
- package/templates/shop/functions/releaseGroup.ts +36 -0
- package/templates/shop/functions/reserveCart.ts +87 -0
- package/templates/shop/functions/restockProduct.ts +23 -0
- package/templates/shop/functions/seedProducts.ts +30 -0
- package/templates/shop/functions/stripeWebhook.ts +72 -0
- package/templates/shop/gitignore +10 -0
- package/templates/shop/lib/owner.ts +26 -0
- package/templates/shop/lib/shop.ts +45 -0
- package/templates/shop/lib/site.config.ts +198 -0
- package/templates/shop/lib/utils.ts +10 -0
- package/templates/shop/package.json +35 -0
- package/templates/shop/tsconfig.json +18 -0
- package/templates/waitlist/.env.example +12 -0
- package/templates/waitlist/AGENTS.md +61 -0
- package/templates/waitlist/README.md +81 -0
- package/templates/waitlist/app/auth-form.tsx +129 -0
- package/templates/waitlist/app/dashboard/dashboard-client.tsx +297 -0
- package/templates/waitlist/app/dashboard/page.tsx +70 -0
- package/templates/waitlist/app/error.tsx +26 -0
- package/templates/waitlist/app/globals.css +148 -0
- package/templates/waitlist/app/layout.tsx +158 -0
- package/templates/waitlist/app/login/page.tsx +39 -0
- package/templates/waitlist/app/not-found.tsx +19 -0
- package/templates/waitlist/app/page.tsx +119 -0
- package/templates/waitlist/app/robots.ts +12 -0
- package/templates/waitlist/app/sitemap.ts +9 -0
- package/templates/waitlist/app/waitlist-hero.tsx +219 -0
- package/templates/waitlist/app.ts +134 -0
- package/templates/waitlist/components/marketing.tsx +96 -0
- package/templates/waitlist/components/ui/button.tsx +56 -0
- package/templates/waitlist/components/ui/card.tsx +90 -0
- package/templates/waitlist/components.json +20 -0
- package/templates/waitlist/functions/joinWaitlist.ts +82 -0
- package/templates/waitlist/functions/waitlistStats.ts +75 -0
- package/templates/waitlist/gitignore +10 -0
- package/templates/waitlist/lib/owner.ts +26 -0
- package/templates/waitlist/lib/site.config.ts +178 -0
- package/templates/waitlist/lib/stats.ts +30 -0
- package/templates/waitlist/lib/utils.ts +10 -0
- package/templates/waitlist/package.json +34 -0
- package/templates/waitlist/tsconfig.json +18 -0
package/bin/create-pylon.js
CHANGED
|
@@ -111,6 +111,72 @@ const TEMPLATE_REGISTRY = {
|
|
|
111
111
|
platforms: [],
|
|
112
112
|
unified: true,
|
|
113
113
|
},
|
|
114
|
+
waitlist: {
|
|
115
|
+
blurb:
|
|
116
|
+
"Coming-soon landing — email capture + a LIVE signup counter (two tabs, no refresh) + owner dashboard. One SSR app.",
|
|
117
|
+
platforms: [],
|
|
118
|
+
unified: true,
|
|
119
|
+
},
|
|
120
|
+
"local-service": {
|
|
121
|
+
blurb:
|
|
122
|
+
"Appointment business — services + booking with LIVE slot availability (a slot greys out for everyone the instant it's booked) + owner dashboard. One SSR app.",
|
|
123
|
+
platforms: [],
|
|
124
|
+
unified: true,
|
|
125
|
+
},
|
|
126
|
+
saas: {
|
|
127
|
+
blurb:
|
|
128
|
+
"SaaS app — marketing landing + multi-tenant dashboard (orgs, members, tenant-scoped data). The 'saas' archetype; identical to the default template.",
|
|
129
|
+
platforms: [],
|
|
130
|
+
unified: true,
|
|
131
|
+
},
|
|
132
|
+
restaurant: {
|
|
133
|
+
blurb:
|
|
134
|
+
"Restaurant — menu + reservations with LIVE table availability (each seating shows tables-left, greys to 'Full' instantly) + owner dashboard. One SSR app.",
|
|
135
|
+
platforms: [],
|
|
136
|
+
unified: true,
|
|
137
|
+
},
|
|
138
|
+
creator: {
|
|
139
|
+
blurb:
|
|
140
|
+
"Personal brand / creator — bio + offerings + a newsletter signup with a LIVE subscriber counter + owner dashboard. One SSR app.",
|
|
141
|
+
platforms: [],
|
|
142
|
+
unified: true,
|
|
143
|
+
},
|
|
144
|
+
shop: {
|
|
145
|
+
blurb:
|
|
146
|
+
"DTC store — product grid with LIVE inventory (stock ticks down, flips to 'Sold out' instantly), cart + real Stripe checkout (graceful no-key fallback) + owner dashboard (orders + stock). One SSR app.",
|
|
147
|
+
platforms: [],
|
|
148
|
+
unified: true,
|
|
149
|
+
},
|
|
150
|
+
agency: {
|
|
151
|
+
blurb:
|
|
152
|
+
"Design/dev studio — services, case-study portfolio, team + a project inquiry form. LIVE availability (hero shows open project slots; booking a lead from the dashboard drops it instantly) + owner pipeline. Clearly-marked photo placeholders. One SSR app.",
|
|
153
|
+
platforms: [],
|
|
154
|
+
unified: true,
|
|
155
|
+
},
|
|
156
|
+
marketplace: {
|
|
157
|
+
blurb:
|
|
158
|
+
"Two-sided marketplace — SSR browse grid + listing detail pages (SEO) with REALTIME offers, a live 'just listed' ticker, buy-now/make-offer/watch, and per-user inbox. Email/password auth, owner-stamped listings/offers. Multi-user (no single owner). One SSR app.",
|
|
159
|
+
platforms: [],
|
|
160
|
+
unified: true,
|
|
161
|
+
},
|
|
162
|
+
directory: {
|
|
163
|
+
blurb:
|
|
164
|
+
"Curated directory — LIVE full-text search + category facets (db.useSearch), community upvotes that tick up across tabs, and a moderated submit flow (deny-all PII → curator approves → public). Showcases Pylon FTS. One SSR app.",
|
|
165
|
+
platforms: [],
|
|
166
|
+
unified: true,
|
|
167
|
+
},
|
|
168
|
+
"ai-chat": {
|
|
169
|
+
blurb:
|
|
170
|
+
"Streaming AI chat — token streaming via the built-in /api/ai/stream (your key stays server-side), multi-conversation history that's owner-scoped + synced across tabs in realtime, guest or signed-in. Set PYLON_AI_API_KEY to enable. One SSR app.",
|
|
171
|
+
platforms: [],
|
|
172
|
+
unified: true,
|
|
173
|
+
},
|
|
174
|
+
"ai-studio": {
|
|
175
|
+
blurb:
|
|
176
|
+
"Generative media studio (image/audio/video) — a LIVE gallery that fills in as each background job finishes (pending card → result, synced across tabs). Owner-scoped generations; provider call + key stay server-side. Boots with a no-key placeholder; set REPLICATE_API_TOKEN for real image/audio/video. One SSR app.",
|
|
177
|
+
platforms: [],
|
|
178
|
+
unified: true,
|
|
179
|
+
},
|
|
114
180
|
};
|
|
115
181
|
const TEMPLATES_AVAILABLE = Object.keys(TEMPLATE_REGISTRY);
|
|
116
182
|
|
|
@@ -176,6 +242,16 @@ Examples:
|
|
|
176
242
|
npm create @pylonsync/pylon my-app --template todo # live, optimistic todo (SSR, one port)
|
|
177
243
|
npm create @pylonsync/pylon my-app --template b2b # minimal multi-tenant (orgs, members, RBAC)
|
|
178
244
|
npm create @pylonsync/pylon my-app --template chat # realtime live chat room
|
|
245
|
+
npm create @pylonsync/pylon my-app --template waitlist # coming-soon landing + live signup counter
|
|
246
|
+
npm create @pylonsync/pylon my-app --template local-service # appointment business + live booking availability
|
|
247
|
+
npm create @pylonsync/pylon my-app --template restaurant # restaurant menu + live reservation availability
|
|
248
|
+
npm create @pylonsync/pylon my-app --template creator # personal brand + live newsletter subscriber count
|
|
249
|
+
npm create @pylonsync/pylon my-app --template shop # DTC store + live inventory
|
|
250
|
+
npm create @pylonsync/pylon my-app --template agency # studio/agency + live project availability
|
|
251
|
+
npm create @pylonsync/pylon my-app --template marketplace # two-sided marketplace + realtime offers
|
|
252
|
+
npm create @pylonsync/pylon my-app --template directory # searchable directory + facets + live upvotes
|
|
253
|
+
npm create @pylonsync/pylon my-app --template ai-chat # streaming LLM chat + synced history
|
|
254
|
+
npm create @pylonsync/pylon my-app --template ai-studio # generative media studio + live gallery
|
|
179
255
|
`);
|
|
180
256
|
exit(0);
|
|
181
257
|
}
|
|
@@ -201,6 +277,10 @@ if (!flags.template) {
|
|
|
201
277
|
// `ssr` was the original name of the default template; keep it working as a
|
|
202
278
|
// quiet alias so older `--template ssr` invocations don't break.
|
|
203
279
|
if (flags.template === "ssr") flags.template = "default";
|
|
280
|
+
// `saas` is the archetype NAME for the default template (a SaaS landing +
|
|
281
|
+
// multi-tenant dashboard). It scaffolds the exact same app as `default`; we
|
|
282
|
+
// alias rather than duplicate the whole tree so there's one source of truth.
|
|
283
|
+
if (flags.template === "saas") flags.template = "default";
|
|
204
284
|
// `unified` templates (default) are a single app, not a monorepo — they take
|
|
205
285
|
// no platforms. Skip the platform prompt + validation for them entirely.
|
|
206
286
|
const isUnified = TEMPLATE_REGISTRY[flags.template]?.unified === true;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pylonsync/create-pylon",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.276",
|
|
4
4
|
"description": "Scaffold a new Pylon app — realtime backend + web/mobile/expo frontends in one command. Run via `npm create @pylonsync/pylon@latest`.",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
# Landing-page archetype templates
|
|
2
|
+
|
|
3
|
+
Default templates Mast picks from when it builds a site for a new business. Each
|
|
4
|
+
is a real `create-pylon` template (landing + dashboard + a live backend), so
|
|
5
|
+
they're useful to every Pylon user too — Mast just adds a classifier that maps a
|
|
6
|
+
business description to one of these and fills its config.
|
|
7
|
+
|
|
8
|
+
## Principles
|
|
9
|
+
- **An archetype is a section structure + one real realtime feature, not just
|
|
10
|
+
copy/colors.** Anyone can have an LLM emit a static landing page now. The edge
|
|
11
|
+
is that the generated site has a live backend doing something. Every template
|
|
12
|
+
ships with exactly one genuinely-realtime hook.
|
|
13
|
+
- **Shared foundation.** All templates share the brand/colors/seo config base
|
|
14
|
+
(see the default template's `lib/site.config.ts`) and the marketing component
|
|
15
|
+
kit (`WRAP`, `Eyebrow`, `SectionHead`, `FeatureGrid`, `Shot`, etc.). An
|
|
16
|
+
archetype adds its own sections, config slots, and backend entities.
|
|
17
|
+
- **Customization = generate one `site.config.ts`** (plus optional seed rows for
|
|
18
|
+
archetypes with lists, e.g. services/menu). Same contract as the default.
|
|
19
|
+
- **Privacy is part of the spec.** Landing pages are public. Any entity holding
|
|
20
|
+
visitor PII (emails, phones) must deny client reads — writes go through a
|
|
21
|
+
server action, and only aggregates (a count) or non-PII fields (busy time
|
|
22
|
+
slots) are exposed to the page. Never let a marketing site leak its own
|
|
23
|
+
customers' emails.
|
|
24
|
+
|
|
25
|
+
## Shared config base
|
|
26
|
+
```ts
|
|
27
|
+
type BaseConfig = {
|
|
28
|
+
brand: { name; letter; domain; email; footerBlurb; copyrightName; socials };
|
|
29
|
+
colors: { brand; brandSoft; paper };
|
|
30
|
+
seo: { title; description };
|
|
31
|
+
};
|
|
32
|
+
```
|
|
33
|
+
Each archetype below is `BaseConfig & { ...archetype slots }`.
|
|
34
|
+
|
|
35
|
+
## Mast classifier contract
|
|
36
|
+
Input: the business description. Output:
|
|
37
|
+
```ts
|
|
38
|
+
{ template: "waitlist" | "local-service" | "saas" | "restaurant" | "shop" | "creator" | "agency" | "marketplace" | "directory",
|
|
39
|
+
siteConfig: <that template's typed config>,
|
|
40
|
+
seed?: { services?: [...]; menu?: [...] } // list rows for archetypes that have them
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
Ambiguous or clearly pre-revenue → `waitlist` (safe universal default). Known
|
|
44
|
+
operating business → the matching archetype.
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
# 1. `waitlist` — pre-launch / coming-soon ★ build first (dogfood target)
|
|
49
|
+
|
|
50
|
+
**Who:** validating demand, "I just started," pre-revenue, or fallback when the
|
|
51
|
+
business type is unclear.
|
|
52
|
+
|
|
53
|
+
**Sections (single page):**
|
|
54
|
+
1. Hero — badge ("Coming soon"), headline, subcopy, **email capture form + live
|
|
55
|
+
signup counter** ("1,247 people waiting").
|
|
56
|
+
2. Value props — 3 items (what it is / who it's for / why now).
|
|
57
|
+
3. Social proof — optional logos or quotes.
|
|
58
|
+
4. FAQ — optional, short.
|
|
59
|
+
5. Footer.
|
|
60
|
+
|
|
61
|
+
**Realtime feature:** the signup counter ticks up for everyone with the page
|
|
62
|
+
open. Open two tabs, submit in one, the other increments without refresh. That's
|
|
63
|
+
the whole "it's a real live app" proof.
|
|
64
|
+
|
|
65
|
+
**Config (`WaitlistConfig`):**
|
|
66
|
+
```ts
|
|
67
|
+
BaseConfig & {
|
|
68
|
+
hero: { badge; headline; subcopy; emailPlaceholder; ctaLabel; successMessage };
|
|
69
|
+
counter: { enabled: boolean; label: string; seedCount?: number };
|
|
70
|
+
valueProps: { eyebrow; headline; items: { title; body; icon? }[] };
|
|
71
|
+
socialProof?: { label; logos?: string[]; quotes?: { quote; name; role }[] };
|
|
72
|
+
faq?: { eyebrow; headline; items: { q; a }[] };
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
**Backend (`app.ts`):**
|
|
77
|
+
```ts
|
|
78
|
+
Signup = entity("Signup", {
|
|
79
|
+
email: field.string().unique(), // unique → dedupe
|
|
80
|
+
createdAt: field.datetime(),
|
|
81
|
+
}, { indexes: [{ name: "by_email", fields: ["email"], unique: true }] });
|
|
82
|
+
```
|
|
83
|
+
- `joinWaitlist({ email })` — public action: validate + lowercase + dedupe +
|
|
84
|
+
rate-limit, insert. Returns `{ ok, alreadyJoined? }`. Never returns other rows.
|
|
85
|
+
- `waitlistCount()` — query returning `count(Signup)`; the page live-subscribes
|
|
86
|
+
so the number updates on every insert.
|
|
87
|
+
- **Privacy:** `Signup` policy denies ALL client read/write (like
|
|
88
|
+
`cliAuthCodePolicy`). The page only ever sees the count, never emails.
|
|
89
|
+
|
|
90
|
+
**Dashboard:** total signups, signups-over-time chart, searchable list, CSV
|
|
91
|
+
export.
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
# 2. `local-service` — appointment businesses (booking) ★ build second
|
|
96
|
+
|
|
97
|
+
**Who:** salon, barber, trades (plumber/electrician/cleaner), trainer, clinic,
|
|
98
|
+
studio, tutor — anyone who sells time slots.
|
|
99
|
+
|
|
100
|
+
**Sections:**
|
|
101
|
+
1. Hero — business name, tagline, **"Book now"** CTA, hero image, quick facts
|
|
102
|
+
(hours / area / phone).
|
|
103
|
+
2. Services + prices — list/grid: name, duration, price.
|
|
104
|
+
3. **Booking** — pick service → pick a time from **live availability** → name /
|
|
105
|
+
email / phone → confirm.
|
|
106
|
+
4. Reviews.
|
|
107
|
+
5. Hours + location — address, map embed, hours, contact.
|
|
108
|
+
6. Gallery — optional.
|
|
109
|
+
7. FAQ — optional. Footer.
|
|
110
|
+
|
|
111
|
+
**Realtime feature:** live slot availability. The time picker subscribes to the
|
|
112
|
+
day's bookings; the moment someone books a slot it greys out for everyone else —
|
|
113
|
+
no double-booking. Server also re-checks at insert time to close the race.
|
|
114
|
+
|
|
115
|
+
**Config (`LocalServiceConfig`):**
|
|
116
|
+
```ts
|
|
117
|
+
BaseConfig & {
|
|
118
|
+
hero: { tagline; headline; subcopy; ctaLabel; heroImage?;
|
|
119
|
+
quickFacts: { hours; area; phone } };
|
|
120
|
+
services: { eyebrow; headline;
|
|
121
|
+
items: { slug; name; durationMin; price; description? }[] };
|
|
122
|
+
booking: { enabled; headline; slotMinutes; // e.g. 30
|
|
123
|
+
hours: { [day in 0..6]: { open; close } | null }; // weekly
|
|
124
|
+
leadTimeHours?; confirmationMessage };
|
|
125
|
+
reviews?: { eyebrow; headline; items: { quote; name; rating? }[] };
|
|
126
|
+
location: { address; mapEmbedUrl?; hoursText; phone; email };
|
|
127
|
+
gallery?: { images: string[] };
|
|
128
|
+
faq?: { eyebrow; headline; items: { q; a }[] };
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
**Backend (`app.ts`):**
|
|
133
|
+
```ts
|
|
134
|
+
Service = entity("Service", {
|
|
135
|
+
slug; name; durationMin: field.int(); priceCents: field.int();
|
|
136
|
+
description: field.string().optional(); active: field.bool();
|
|
137
|
+
});
|
|
138
|
+
Booking = entity("Booking", {
|
|
139
|
+
serviceId: field.id("Service");
|
|
140
|
+
startsAt: field.datetime(); endsAt: field.datetime();
|
|
141
|
+
customerName; customerEmail; customerPhone: field.string().optional();
|
|
142
|
+
status: field.string(); // "confirmed" | "cancelled"
|
|
143
|
+
createdAt: field.datetime();
|
|
144
|
+
}, { indexes: [{ name: "by_start", fields: ["startsAt"] }] });
|
|
145
|
+
```
|
|
146
|
+
- `createBooking({ serviceId, startsAt, customer })` — public action: server-side
|
|
147
|
+
re-check that the slot is still free (overlap query) before insert, to close
|
|
148
|
+
the race the live UI already mostly prevents.
|
|
149
|
+
- `bookedSlotsForRange({ from, to })` — query returning only `{ startsAt, endsAt }`
|
|
150
|
+
(NO customer fields); the picker subscribes and computes free slots = hours −
|
|
151
|
+
booked. **Privacy:** `Booking` denies client read of full rows; only this
|
|
152
|
+
PII-stripped projection is exposed. `Service` is public-read (it's menu data).
|
|
153
|
+
|
|
154
|
+
**Dashboard:** day/week calendar of bookings, confirm/cancel, manage services +
|
|
155
|
+
weekly hours.
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
# 3. `saas` — app / tool / digital product (≈ the current default, renamed)
|
|
160
|
+
|
|
161
|
+
**Who:** software founders ("I'm building an app").
|
|
162
|
+
**Sections:** hero + dashboard preview, logo cloud, feature sections
|
|
163
|
+
(products), pricing tiers, testimonials, FAQ. (Already built — this is the
|
|
164
|
+
refactored `default` template; rename `default` → `saas`, keep `default` as an
|
|
165
|
+
alias.)
|
|
166
|
+
**Realtime feature:** the live dashboard behind "Open dashboard" (the workspace
|
|
167
|
+
itself).
|
|
168
|
+
**Config:** the existing `SiteConfig` (`lib/site.config.ts`).
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
# 4. `restaurant` — food & hospitality
|
|
173
|
+
|
|
174
|
+
**Who:** restaurant, cafe, bar, food truck, bakery.
|
|
175
|
+
**Sections:** hero (name + "Reserve" / "Order"), **menu** (sections → items with
|
|
176
|
+
price/description), hours + location + map, reservations or order-ahead, gallery,
|
|
177
|
+
reviews, footer.
|
|
178
|
+
**Realtime feature:** live table/reservation availability (same engine as
|
|
179
|
+
local-service booking) OR live order-ahead status board.
|
|
180
|
+
**Config highlights:** `menu: { sections: { name; items: { name; price; desc?;
|
|
181
|
+
tags? }[] }[] }`, `reservations` (reuse booking shape), `location`, `hours`.
|
|
182
|
+
**Entities:** `MenuItem` (public read), `Reservation` (PII-private, like
|
|
183
|
+
`Booking`).
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
# 5. `shop` — DTC product / small store
|
|
188
|
+
|
|
189
|
+
**Who:** product brands, makers, single-product or small-catalog sellers.
|
|
190
|
+
**Sections:** hero, featured products / product grid, value props, reviews,
|
|
191
|
+
shipping & returns, footer; cart.
|
|
192
|
+
**Realtime feature:** live inventory (stock count updates as orders land) + live
|
|
193
|
+
cart count.
|
|
194
|
+
**Config highlights:** `products: { items: { slug; name; priceCents; image;
|
|
195
|
+
description; stock? }[] }`, `valueProps`, `policies` (shipping/returns text).
|
|
196
|
+
**Entities:** `Product` (public read, live stock), `Order` (PII-private; cart
|
|
197
|
+
lines share an `orderGroupId`).
|
|
198
|
+
**Checkout:** real **Stripe Checkout** via `@pylonsync/stripe`'s `stripeRequest`
|
|
199
|
+
+ `verifyStripeSignature` (one-time `price_data` line items; signed webhook at
|
|
200
|
+
`/api/webhooks/stripeWebhook` settles paid / releases held stock on expiry).
|
|
201
|
+
Stock is held under a per-product advisory lock at checkout so the cart can't
|
|
202
|
+
oversell. **Graceful degradation:** with no `STRIPE_SECRET_KEY` the order is held
|
|
203
|
+
as `reserved` for the owner to follow up — the store boots and demos live
|
|
204
|
+
inventory with zero config.
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
# 6. `creator` — personal brand / coach / consultant
|
|
209
|
+
|
|
210
|
+
**Who:** solo creators, coaches, consultants, freelancers, newsletter authors.
|
|
211
|
+
**Sections:** hero (you), about, offerings/services, portfolio or testimonials,
|
|
212
|
+
**lead magnet / newsletter signup**, book-a-call, links, footer.
|
|
213
|
+
**Realtime feature:** live newsletter signup count (reuse the `waitlist`
|
|
214
|
+
`Signup` + counter) and/or live call-booking availability (reuse `local-service`
|
|
215
|
+
booking).
|
|
216
|
+
**Config highlights:** `profile: { name; tagline; photo; bio }`, `offerings`,
|
|
217
|
+
`portfolio?`, `newsletter` (reuse waitlist counter), `booking?` (reuse
|
|
218
|
+
local-service).
|
|
219
|
+
**Entities:** `Signup` and/or `Booking`, reused from the other archetypes.
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
# 7. `agency` — design / dev / marketing studio
|
|
224
|
+
|
|
225
|
+
**Who:** boutique product/design/dev/marketing studios that take on a limited
|
|
226
|
+
number of clients at once.
|
|
227
|
+
**Sections:** hero (+ live availability pill), logo cloud, services, **work /
|
|
228
|
+
case-study grid** (marked project-shot placeholders), process, **team** (marked
|
|
229
|
+
headshot placeholders), testimonials, **project inquiry form** (#contact), footer.
|
|
230
|
+
**Realtime feature:** scarcity — a public `Capacity` row holds the booking window
|
|
231
|
+
+ open project slots; the hero pill (`db.useQuery("Capacity")`) shows "N slots
|
|
232
|
+
open" live, and the owner booking a lead from the dashboard decrements it for
|
|
233
|
+
everyone instantly (same shape as `shop` inventory).
|
|
234
|
+
**Config highlights:** `hero`, `capacity: { label; openSlots }` (seed), `logos`,
|
|
235
|
+
`services`, `work: CaseStudy[]`, `process`, `team: TeamMember[]`, `testimonials?`,
|
|
236
|
+
`contact: { projectTypes[]; budgets[]; confirmationMessage }`.
|
|
237
|
+
**Entities:** `Inquiry` (PII deny-all: name/email/company/budget/message + status),
|
|
238
|
+
`Capacity` (public-read single row, live openSlots), `User`.
|
|
239
|
+
**Placeholders:** hero photo, each case-study project shot, each team headshot —
|
|
240
|
+
all clearly marked via the shared `ImagePlaceholder`.
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
# 8. `marketplace` — two-sided buy/sell platform (ported from examples/market)
|
|
245
|
+
|
|
246
|
+
**Who:** local/vertical marketplaces, classifieds, gear resale, any buyer↔seller
|
|
247
|
+
platform.
|
|
248
|
+
**Sections:** SSR browse grid + category facets (`/`), listing detail (`/listing/:slug`),
|
|
249
|
+
sell form (`/sell`, sign-in gated), per-user inbox (`/me`).
|
|
250
|
+
**Realtime feature:** a live "just listed" ticker + live offers on a listing +
|
|
251
|
+
your `/me` inbox, all via `db.useQuery` over public `Listing`/`Offer` (and
|
|
252
|
+
private `Watch`). List in one tab → it appears in another instantly.
|
|
253
|
+
**Entities:** `User`, `Listing` (`sellerId: field.owner()`, slug, price,
|
|
254
|
+
category, condition, status, seed-gradient photo), `Offer` (`buyerId:
|
|
255
|
+
field.owner()`, amount, status), `Watch` (private). Public-read listings/offers,
|
|
256
|
+
owner-scoped writes; accept-marks-sold-and-declines-siblings in
|
|
257
|
+
`respondToOffer`. **Multi-user** (email/password, no single owner) — unlike the
|
|
258
|
+
single-tenant archetypes above.
|
|
259
|
+
**Note:** ported from `examples/market` (not config-driven — rebrand in
|
|
260
|
+
`app/layout.tsx` + `functions/seedMarket.ts`). Listing photos are generated
|
|
261
|
+
gradients; swap for real `<img>` + `/api/files` upload for production.
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
# 9. `directory` — curated, searchable listing site
|
|
266
|
+
|
|
267
|
+
**Who:** "best X" lists, tool/company/local directories, awesome-lists with a UI.
|
|
268
|
+
**Sections:** hero + a live faceted-search browse (`#browse`), a `/submit` page,
|
|
269
|
+
a `/dashboard` moderation queue.
|
|
270
|
+
**Realtime feature:** the showcase for Pylon **FTS + facets** — `db.useSearch`
|
|
271
|
+
re-runs on every keystroke AND on every write, so it doubles as the live layer;
|
|
272
|
+
public `upvote` bumps `Listing.votes` and the count ticks up across all tabs.
|
|
273
|
+
**Entities:** `Listing` (public-read, no PII; `search: { text, facets, sortable }`
|
|
274
|
+
→ FTS5 + facet shadow tables) + `Submission` (deny-all PII: submitter
|
|
275
|
+
name/email + proposed entry, status) + `User`. submitListing/upvote public;
|
|
276
|
+
submissionsForOwner/approveSubmission/rejectSubmission owner-gated;
|
|
277
|
+
approveSubmission copies ONLY public fields into a Listing (PII never published).
|
|
278
|
+
**Key API:** `db.useSearch<T>(entity, { query, filters, facets, sort:[field,dir],
|
|
279
|
+
page, pageSize })` → `{ hits, facetCounts, total, loading }` (reference:
|
|
280
|
+
examples/store/client/Catalog.tsx).
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
# 10. `ai-chat` — streaming AI assistant
|
|
285
|
+
|
|
286
|
+
**Who:** any LLM chat product / internal assistant.
|
|
287
|
+
**Sections:** a full-screen chat — conversation sidebar + streaming thread +
|
|
288
|
+
composer (`/`), optional `/login`.
|
|
289
|
+
**Realtime feature:** token streaming from the built-in `POST /api/ai/stream`
|
|
290
|
+
(SSE; the API key never leaves the server), PLUS owner-scoped `Conversation` +
|
|
291
|
+
`Message` synced across the user's tabs via `db.useQuery`.
|
|
292
|
+
**Entities:** `Conversation` + `Message` (both `userId: field.owner()`,
|
|
293
|
+
owner-scoped read/write — private per user; messages written with optimistic
|
|
294
|
+
`db.insert`, no custom functions) + `User`. Multi-user (guest or signed-in).
|
|
295
|
+
**Config:** `PYLON_AI_PROVIDER` + `PYLON_AI_API_KEY` + `PYLON_AI_MODEL` enable
|
|
296
|
+
replies; without them the app boots and shows a friendly "configure AI" notice.
|
|
297
|
+
**SSE contract:** `data: {"choices":[{"delta":{"content":"…"}}]}` … `data:
|
|
298
|
+
[DONE]`; 503 `AI_NOT_CONFIGURED` / 429 `RATE_LIMITED` shims handled in the client.
|
|
299
|
+
|
|
300
|
+
---
|
|
301
|
+
|
|
302
|
+
# 11. `ai-studio` — generative media studio (image / audio / video)
|
|
303
|
+
|
|
304
|
+
**Who:** AI image/audio/video generators, creative tools.
|
|
305
|
+
**Sections:** a prompt bar + medium selector over a live gallery (`/`), optional
|
|
306
|
+
`/login`.
|
|
307
|
+
**Realtime feature:** the generation gallery — the `generate` action inserts a
|
|
308
|
+
`pending` Generation (card appears instantly), runs the provider call
|
|
309
|
+
server-side, then flips the row to `done`/`failed`; `db.useQuery` syncs that to
|
|
310
|
+
every open tab so the card updates live.
|
|
311
|
+
**Entities:** `Generation` (owner-scoped READ, `allowInsert:"false"` — only the
|
|
312
|
+
server pipeline writes it) + `User`. Multi-user (guest or signed-in).
|
|
313
|
+
**Functions:** `generate` (public action) brackets the provider call with
|
|
314
|
+
internal `_createGeneration` / `_finishGeneration` mutations. Image → OpenAI
|
|
315
|
+
Images, audio → OpenAI TTS (both when `OPENAI_API_KEY` is set), video → a labeled
|
|
316
|
+
extension point. **No key → a clearly-labeled placeholder** (SVG for image) so
|
|
317
|
+
the flow + gallery work with zero config. Key + media stay server-side.
|
|
318
|
+
**Note:** image uses the provider's hosted URL (small to sync, ~1h TTL);
|
|
319
|
+
persist via `/api/files` for permanence. lib/studio.ts holds the placeholder gen.
|
|
320
|
+
|
|
321
|
+
---
|
|
322
|
+
|
|
323
|
+
## Build order
|
|
324
|
+
1. **`waitlist`** — smallest, proves the whole pipeline, already the dogfood
|
|
325
|
+
target. (`Signup` + counter + `joinWaitlist`.)
|
|
326
|
+
2. **`local-service`** — biggest SMB market; booking is the strongest realtime
|
|
327
|
+
demo. (`Service` + `Booking` + live availability.)
|
|
328
|
+
3. **`saas`** — rename/finish the refactored default.
|
|
329
|
+
4. **`restaurant`**, **`shop`**, **`creator`** — breadth; each heavily reuses the
|
|
330
|
+
`booking`/`signup` engines above, so they're mostly config + a couple of
|
|
331
|
+
bespoke sections.
|
|
332
|
+
|
|
333
|
+
## Reuse map (so 6 archetypes aren't 6 from-scratch builds)
|
|
334
|
+
- `Signup` + live counter → waitlist, creator (newsletter).
|
|
335
|
+
- `Booking` + live availability → local-service, restaurant (reservations),
|
|
336
|
+
creator (call booking).
|
|
337
|
+
- `Product` grid + inventory → shop.
|
|
338
|
+
- Marketing component kit + BaseConfig → all.
|
|
339
|
+
Two real backend engines (signup, booking) + one (product) cover every archetype.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Copy to `.env` and fill in. `pylon dev` loads `.env` automatically.
|
|
2
|
+
|
|
3
|
+
# ── Owner (required to use the dashboard) ────────────────────────────────────
|
|
4
|
+
# A studio is single-tenant: one business, one owner. The /dashboard is unlocked
|
|
5
|
+
# only for the account whose email matches this value, and the owner-only data
|
|
6
|
+
# function refuses to return any inquiries (with their PII) otherwise. Set this
|
|
7
|
+
# to the email you'll sign in with, then create that account at /login.
|
|
8
|
+
PYLON_OWNER_EMAIL=you@yourstudio.com
|
|
9
|
+
|
|
10
|
+
# ── Site URL (optional) ──────────────────────────────────────────────────────
|
|
11
|
+
# Used by robots.txt + sitemap.xml. Point it at your real domain in production.
|
|
12
|
+
# SITE_URL=https://yourstudio.com
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# AGENTS.md — working in a Pylon project
|
|
2
|
+
|
|
3
|
+
Operating rules for a coding agent in this Pylon app. Pylon is a Rails-like framework for realtime apps: you declare entities, policies, and server functions in TypeScript, and a single Rust binary (`pylon`) serves the API, auth, sync, WebSocket, SSE, and native React 19 SSR — one process, one port. The full API reference is at **/llms-full.txt** (served at `/llms-full.txt`; in the repo at `apps/web/public/llms-full.txt`). Read it before guessing an API name.
|
|
4
|
+
|
|
5
|
+
## Directory conventions
|
|
6
|
+
|
|
7
|
+
**Unified SSR app:**
|
|
8
|
+
- `app.ts` — data model + manifest (`entity()` + `field.*`, queries/actions/policies, `routes: await discoverAppRoutes()`). Ends with `console.log(JSON.stringify(manifest))`.
|
|
9
|
+
- `app/` — file-based SSR routes. `app/page.tsx` → `/`, `app/about/page.tsx` → `/about`, `app/blog/[slug]/page.tsx` → `/blog/:slug`. `app/layout.tsx` is the shell; `app/error.tsx` / `app/not-found.tsx` are boundaries.
|
|
10
|
+
- `app/globals.css` — Tailwind v4 entrypoint (auto-compiled and injected).
|
|
11
|
+
- `functions/` — server functions, one per file, `default`-exported.
|
|
12
|
+
- `.pylon/` — local dev state (sqlite, jobs, sessions, uploads). Created by `pylon dev`. Do not commit.
|
|
13
|
+
|
|
14
|
+
**Monorepo app:** backend is `apps/api/` (entry `apps/api/schema.ts`, handlers in `apps/api/functions/`); frontend in `apps/web/`. `pylon.manifest.json` / `pylon.client.ts` are generated — do not hand-edit.
|
|
15
|
+
|
|
16
|
+
## The core authoring loop
|
|
17
|
+
|
|
18
|
+
1. **Define an entity** — `entity("Thing", { name: field.string(), done: field.boolean().default(false) })`. Modifiers: `.optional()`, `.unique()`, `.readonly()` (settable on insert, rejected on client update — use for `authorId`/`orgId`), `.serverOnly()` (never in HTTP responses), `.encrypted()` (AEAD at rest, needs `PYLON_ENCRYPTION_KEY`), `.crdt("text")` (collaborative).
|
|
19
|
+
2. **Write a policy** — `policy({ entity: "Thing", allowRead, allowInsert, allowUpdate, allowDelete })` with CEL-like expressions over `auth.*` / `data.*` (e.g. `"auth.userId == data.authorId"`). **Omitted actions DENY by default.** Wide-open dev policies (`allow*: "true"`) are flagged by `pylon lint` — tighten before shipping.
|
|
20
|
+
3. **Author a function** in `functions/<name>.ts` — `query` (read-only), `mutation` (transactional read+write), or `action` (external I/O, no direct `ctx.db`). Import `{ query, mutation, action, v }` from `@pylonsync/functions`. `auth` defaults to `"user"` (secure-by-default); set `"public"` explicitly for unauthenticated access. Use `ctx.db.*`, `ctx.auth.userId`, `ctx.error(code, msg)`.
|
|
21
|
+
4. **Read it on the client** — `db.useQuery("Thing")` (live, re-renders on any write) or `db.useQueryOne("Thing", id)`. Call functions with `db.fn(name, args)` / `callFn`. On SSR pages, read via `use(serverData.list("Thing"))` inside `<Suspense>`.
|
|
22
|
+
|
|
23
|
+
## Key gotchas
|
|
24
|
+
|
|
25
|
+
- **Policies deny by default; server functions BYPASS them.** Direct client CRUD (`/api/entities/*`) and sync are policy-checked. Functions run with full DB access — enforce trust with `ctx.auth` checks inside the handler, not policies.
|
|
26
|
+
- **Type page props from the SDK, don't hand-roll them.** `import type { PageProps, Metadata } from "@pylonsync/react"`. Every page/layout gets `{ url, params, searchParams, auth, response, serverData }`; `PageProps<{ slug: string }>` types a `[slug]` route's params. Request headers/cookies are intentionally NOT on `PageProps` — they're server-only and stripped from hydration, so reading them in the render would mismatch.
|
|
27
|
+
- **Anonymous output caching is opt-in + earned.** `export const revalidate = 60` (seconds) on a page makes it CDN-cacheable (`public, s-maxage=60`) — but ONLY if the render is auth-INDEPENDENT: it must NOT read `props.auth` (reading it at all opts out, even for anonymous), set no cookie, and the app must not run strict per-caller policies (`PYLON_STRICT_FN_POLICIES`). `export const dynamic = "force-static"` caches until the next deploy; `"force-dynamic"` never caches. Fail-closed: without the opt-in (or if any condition fails) the page is `no-cache`. A page that reads `auth` or sets a cookie is never shared. The SAME earned render is also kept in an **origin disk cache** (`.pylon/.cache/ssr`): a cookie-less GET with no query string is served straight off disk for the TTL — skipping the render entirely — then re-rendered live when stale. The disk cache is namespaced per deploy (wiped on each new build) and OFF in `pylon dev` (so an edit is never masked by a stale entry); invalidation is by the `revalidate` TTL or the next deploy.
|
|
28
|
+
- **No-JS forms use `route.ts` + `<Form>`.** Drop `app/.../route.ts` exporting `export const POST: RouteHandler = async ({ form, db, response, auth }) => { await db.insert("X", {...}); response.redirect("/x?ok=1"); }` (303 POST-redirect-GET by default). Render `<Form action="/x">` (from @pylonsync/react) with plain `<input name=...>` — works with JS off (native POST→handler→redirect) and is enhanced to no-reload when JS is on. The handler's `db` is read+write (mutation trust model — gate on `auth`); CSRF is automatic (Origin gate + SameSite=Lax). Multipart/file uploads aren't supported yet — use urlencoded forms + `/api/files`.
|
|
29
|
+
- **`loading.tsx` streams a skeleton while the page's data resolves.** Drop `app/.../loading.tsx` (default export, page props) and the nearest one becomes a route-level Suspense fallback: Pylon flushes the shell + skeleton immediately, then reveals the real page when its top-level `use(serverData…)` resolves (no blank page). It only shows when the PAGE suspends — a page that wraps its own `<Suspense>` around a child (like `/dashboard` in this template) handles that itself. The skeleton is SERVER-ONLY: don't read `serverData` in it. A page with no `loading.tsx` is buffered (unchanged).
|
|
30
|
+
- **`export const streaming = true` streams a page's OWN inner `<Suspense>` boundaries.** Without it (and without a `loading.tsx`), a page is BUFFERED — the whole document, including suspended children, resolves before the first byte. Opt in and the shell + each inner `<Suspense>` fallback flush immediately, then each boundary's real content streams in as its data resolves (multi-boundary progressive streaming). It's opt-in because it changes the response timing contract: a streaming render commits its HTTP head BEFORE suspended subtrees finish, so (a) it's never CDN/disk cacheable — don't combine with `export const revalidate`; (b) `response.setStatus/setCookie/redirect/notFound` only take effect from the SYNCHRONOUS shell render — a call from inside a suspended subtree is dropped (the runtime logs a loud warning naming what was lost); (c) a `throw` from a deep `<Suspense>` child resolves via its nearest `error.tsx` at HTTP 200, not a 5xx. Hydration is clean for any number of boundaries (the data blob ships before hydration runs). Type the config with `import type { RouteSegmentConfig } from "@pylonsync/react"`.
|
|
31
|
+
- **`error.tsx` / `not-found.tsx` boundaries are HYDRATED (interactive).** `app/.../error.tsx` catches a throw below it (HTTP 500) and receives `{ error: { message, digest }, reset }` (`import type { ErrorBoundaryProps }`) — `reset()` re-attempts the route; the stack NEVER reaches the client (dev overlay + logs only). `app/.../not-found.tsx` renders at 404 (also for `response.notFound()`) and gets the page props (`NotFoundProps`), no `reset`. Both run useState/onClick/hooks.
|
|
32
|
+
- **Client navigation hooks live in @pylonsync/react.** `useRouter()` → `{ push, replace, back, forward, refresh, prefetch }`; `useSearchParams()` → reactive `URLSearchParams`; `usePathname()` → reactive pathname. The hooks are CLIENT-reactive — during SSR they return defaults (empty params / "/"); for server-side URL values read the `url` / `searchParams` page props.
|
|
33
|
+
- **Dynamic + catch-all routes follow Next conventions.** `app/blog/[slug]/page.tsx` → `params.slug`. `app/docs/[...path]/page.tsx` is a catch-all (matches `/docs/a/b/c`; `params.path === "a/b/c"` — `.split("/")` for segments). `app/shop/[[...filters]]/page.tsx` is an optional catch-all (also matches the bare `/shop`, with `params.filters === ""`). A catch-all must be the last segment; static beats dynamic beats catch-all on overlap.
|
|
34
|
+
- **`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.
|
|
35
|
+
- **`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.
|
|
36
|
+
- **`ctx.llm` and `ctx.connections` are on mutation + action only, NOT query** (reactive purity). `action` has no direct `ctx.db` — use `ctx.runQuery` / `ctx.runMutation`.
|
|
37
|
+
- **It's `db.useQueryOne`, not `useOne`.** Validators and field types have aliases: `v.bool`/`v.boolean`, `v.float`/`v.number`.
|
|
38
|
+
- **There is no `ctx.files` or `defineWorkflow`/`defineJob`.** Files go through `<FileUpload>` + `/api/files/*`; deferred execution is `ctx.scheduler.runAfter/runAt/cancel`.
|
|
39
|
+
|
|
40
|
+
## Use the CLI — don't guess
|
|
41
|
+
|
|
42
|
+
| Need | Command |
|
|
43
|
+
|---|---|
|
|
44
|
+
| Run the app (SSR + API, hot reload, one port `:4321`) | `pylon dev` (or `npm run dev`) |
|
|
45
|
+
| Regenerate manifest + typed client | `pylon codegen` (Swift client: `pylon codegen client --target swift`) |
|
|
46
|
+
| Validate / diff / push schema | `pylon schema check` \| `diff` \| `push` |
|
|
47
|
+
| Migrations | `pylon migrate create <name>` \| `plan` \| `apply` |
|
|
48
|
+
| Lint policies (PYL001–PYL004) | `pylon lint --strict` |
|
|
49
|
+
| Tests | `pylon test` |
|
|
50
|
+
| Adversarial security probe | `pylon test:security` |
|
|
51
|
+
| Inspect cloud request logs (agent-safe) | `pylon logs --json --limit 50` |
|
|
52
|
+
| Inspect data / entities | `pylon data entities` \| `pylon data list <Entity>` |
|
|
53
|
+
| Call a function | `pylon fn <name> key=value` |
|
|
54
|
+
| Health snapshot | `pylon status` |
|
|
55
|
+
| Build for prod | `pylon build` |
|
|
56
|
+
| Deploy (Pylon Cloud by default) | `pylon deploy` |
|
|
57
|
+
| Look up an error code | `pylon explain <CODE>` |
|
|
58
|
+
|
|
59
|
+
`--json` works on every command for machine-readable output. Prefer one-shot/agent-safe flags (`pylon logs --limit N`, not a blocking `--follow`).
|
|
60
|
+
|
|
61
|
+
For full signatures, env vars, the complete CLI, and SSR/client/server-primitive details: **/llms-full.txt**.
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# __APP_NAME__
|
|
2
|
+
|
|
3
|
+
A studio / agency site built with [Pylon](https://pylonsync.com) — a
|
|
4
|
+
server-rendered marketing page with **live availability**, a private project
|
|
5
|
+
inquiry form, and an owner dashboard, all from one binary on one port. No
|
|
6
|
+
Next.js, no separate API server.
|
|
7
|
+
|
|
8
|
+
The realtime point: boutique studios take on a few projects at a time. The hero
|
|
9
|
+
shows how many slots are open this quarter, and the moment the owner books a new
|
|
10
|
+
client from the dashboard, that number drops for everyone with the page open.
|
|
11
|
+
|
|
12
|
+
## Develop
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
__RUN_DEV__
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Open http://localhost:4321. Then **open a second tab**, sign in as the owner
|
|
19
|
+
(see below), book a lead from the dashboard, and watch the "N slots open" pill on
|
|
20
|
+
the public page tick down — with no refresh.
|
|
21
|
+
|
|
22
|
+
## How the realtime works
|
|
23
|
+
|
|
24
|
+
- `Capacity` is a public-read, PII-free row holding the booking window + open
|
|
25
|
+
slot count. `app/contact-form.tsx` reads it with `db.useQuery("Capacity")`, so
|
|
26
|
+
the hero's "N project slots open" pill is live everywhere.
|
|
27
|
+
- `functions/submitInquiry.ts` is a public **mutation** — anyone can send a
|
|
28
|
+
project lead. It does NOT consume a slot (a lead isn't a booking).
|
|
29
|
+
- `functions/bookInquiry.ts` / `declineInquiry.ts` are owner-only mutations that
|
|
30
|
+
mark a lead booked/declined AND adjust `Capacity.openSlots` (under an advisory
|
|
31
|
+
lock), so the public counter moves live.
|
|
32
|
+
- `functions/seedCapacity.ts` creates the Capacity row from config on first
|
|
33
|
+
visit (idempotent).
|
|
34
|
+
|
|
35
|
+
## Privacy — read this
|
|
36
|
+
|
|
37
|
+
The `Inquiry` entity holds the prospect's name, email, company, and budget (PII),
|
|
38
|
+
so its policy in `app.ts` **denies every client read and write**. The public page
|
|
39
|
+
only reads `Capacity` (a label + a number). Inquiries come back only through
|
|
40
|
+
`inquiriesForOwner`, gated to the owner server-side — the contact details never
|
|
41
|
+
travel over entity sync.
|
|
42
|
+
|
|
43
|
+
## The owner dashboard
|
|
44
|
+
|
|
45
|
+
`/dashboard` is the pipeline: every lead (with its details), Book / Decline /
|
|
46
|
+
Release actions, and a live availability editor (set the booking window + open
|
|
47
|
+
slots). It updates live as leads land.
|
|
48
|
+
|
|
49
|
+
Set `PYLON_OWNER_EMAIL` in `.env` (see `.env.example`) to the email you'll sign
|
|
50
|
+
in with, then create that account at `/login`.
|
|
51
|
+
|
|
52
|
+
## Placeholders to replace
|
|
53
|
+
|
|
54
|
+
The page renders **clearly marked** placeholders anywhere a real photo belongs —
|
|
55
|
+
swap each for an `<img>` when you have the asset:
|
|
56
|
+
|
|
57
|
+
- the hero photo ("A photo of your studio or work")
|
|
58
|
+
- each case study's project shot (the `#work` grid)
|
|
59
|
+
- each team member's headshot
|
|
60
|
+
|
|
61
|
+
The logo cloud uses sample names — replace with your real client logos.
|
|
62
|
+
|
|
63
|
+
## Rebrand it
|
|
64
|
+
|
|
65
|
+
Everything lives in **`lib/site.config.ts`** — brand, colors, hero, services,
|
|
66
|
+
case studies, process, team, testimonials, and the contact form's project-type
|
|
67
|
+
and budget options. Edit that one file (or have Mast generate it) and the whole
|
|
68
|
+
studio re-themes; the capacity re-seeds on a fresh database.
|
|
69
|
+
|
|
70
|
+
## Layout
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
app.ts Inquiry (PII) + Capacity (public, live) + User
|
|
74
|
+
lib/site.config.ts ALL copy + brand + work/team/services (edit this)
|
|
75
|
+
functions/seedCapacity.ts idempotent capacity seed from config
|
|
76
|
+
functions/submitInquiry.ts public mutation: write a lead (PII), no slot change
|
|
77
|
+
functions/inquiriesForOwner.ts owner-only query: leads + PII
|
|
78
|
+
functions/{book,decline}Inquiry.ts, setCapacity.ts owner-only mutations
|
|
79
|
+
app/page.tsx the studio site (server-rendered)
|
|
80
|
+
app/contact-form.tsx client island: live slots pill + inquiry form
|
|
81
|
+
app/dashboard/ owner dashboard (auth-gated, live)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Deploy
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
pylon deploy
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Docs: https://docs.pylonsync.com
|