@pylonsync/create-pylon 0.3.295 → 0.3.297

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 (72) hide show
  1. package/package.json +1 -1
  2. package/templates/agency/app/globals.css +8 -1
  3. package/templates/agency/app/layout.tsx +4 -6
  4. package/templates/agency/app.ts +15 -0
  5. package/templates/agency/components/marketing.tsx +2 -4
  6. package/templates/agency/lib/site.config.ts +3 -5
  7. package/templates/ai-chat/app/globals.css +8 -1
  8. package/templates/ai-chat/app/layout.tsx +4 -8
  9. package/templates/ai-chat/app.ts +18 -5
  10. package/templates/ai-chat/lib/site.config.ts +3 -3
  11. package/templates/ai-studio/app/globals.css +8 -1
  12. package/templates/ai-studio/app/layout.tsx +7 -11
  13. package/templates/ai-studio/app.ts +20 -7
  14. package/templates/ai-studio/lib/site.config.ts +2 -2
  15. package/templates/ai-studio/lib/studio.ts +1 -1
  16. package/templates/backend/b2b/apps/api/schema.ts +10 -24
  17. package/templates/backend/consumer/apps/api/schema.ts +4 -7
  18. package/templates/barebones/app/layout.tsx +3 -3
  19. package/templates/barebones/app.ts +2 -3
  20. package/templates/chat/app.ts +3 -9
  21. package/templates/consumer/app.ts +2 -3
  22. package/templates/creator/.env.example +3 -3
  23. package/templates/creator/app/globals.css +8 -1
  24. package/templates/creator/app/layout.tsx +4 -6
  25. package/templates/creator/app.ts +23 -10
  26. package/templates/creator/components/marketing.tsx +2 -4
  27. package/templates/default/app/globals.css +8 -1
  28. package/templates/default/app/layout.tsx +6 -14
  29. package/templates/default/app.ts +15 -0
  30. package/templates/default/lib/products.ts +2 -3
  31. package/templates/default/lib/site.config.ts +1 -2
  32. package/templates/default/lib/site.ts +3 -4
  33. package/templates/directory/app/auth-form.tsx +5 -5
  34. package/templates/directory/app/globals.css +8 -1
  35. package/templates/directory/app/layout.tsx +4 -6
  36. package/templates/directory/app/sitemap.ts +1 -1
  37. package/templates/directory/app.ts +15 -0
  38. package/templates/directory/lib/owner.ts +5 -5
  39. package/templates/expo/chat/apps/expo/App.tsx +2 -3
  40. package/templates/local-service/app/auth-form.tsx +4 -4
  41. package/templates/local-service/app/globals.css +8 -1
  42. package/templates/local-service/app/layout.tsx +4 -6
  43. package/templates/local-service/app/sitemap.ts +1 -1
  44. package/templates/local-service/app.ts +15 -0
  45. package/templates/local-service/components/marketing.tsx +2 -4
  46. package/templates/local-service/lib/owner.ts +3 -3
  47. package/templates/local-service/lib/site.config.ts +4 -6
  48. package/templates/marketplace/app/listing/[id]/page.tsx +3 -3
  49. package/templates/marketplace/functions/makeOffer.ts +0 -1
  50. package/templates/restaurant/app/auth-form.tsx +5 -5
  51. package/templates/restaurant/app/globals.css +8 -1
  52. package/templates/restaurant/app/layout.tsx +4 -6
  53. package/templates/restaurant/app/sitemap.ts +1 -1
  54. package/templates/restaurant/app.ts +15 -0
  55. package/templates/restaurant/lib/owner.ts +5 -5
  56. package/templates/shop/app/auth-form.tsx +5 -5
  57. package/templates/shop/app/globals.css +8 -1
  58. package/templates/shop/app/layout.tsx +5 -7
  59. package/templates/shop/app/sitemap.ts +1 -1
  60. package/templates/shop/app.ts +15 -0
  61. package/templates/shop/lib/owner.ts +6 -5
  62. package/templates/todo/app/layout.tsx +2 -2
  63. package/templates/todo/app/sitemap.ts +1 -1
  64. package/templates/todo/app.ts +4 -5
  65. package/templates/vite/todo/apps/web/vite.config.ts +5 -9
  66. package/templates/waitlist/app/globals.css +8 -1
  67. package/templates/waitlist/app/layout.tsx +4 -6
  68. package/templates/waitlist/app/waitlist-hero.tsx +4 -5
  69. package/templates/waitlist/app.ts +26 -9
  70. package/templates/waitlist/lib/site.config.ts +3 -5
  71. package/templates/web/barebones/apps/web/postcss.config.mjs +0 -1
  72. package/templates/web/barebones/apps/web/src/app/globals.css +1 -4
@@ -5,6 +5,7 @@ import {
5
5
  auth,
6
6
  buildManifest,
7
7
  discoverAppRoutes,
8
+ font,
8
9
  } from "@pylonsync/sdk";
9
10
 
10
11
  // ---------------------------------------------------------------------------
@@ -126,6 +127,20 @@ const manifest = buildManifest({
126
127
  actions: [],
127
128
  policies: [productPolicy, orderPolicy, userPolicy],
128
129
  auth: auth(),
130
+ // Self-hosted Inter (next/font parity): the build fetches the woff2, serves it
131
+ // same-origin (no third-party request, no FOUT), preloads it, and synthesizes a
132
+ // size-adjusted fallback face so there's no layout shift. globals.css reads it
133
+ // via `var(--font-sans, …)`; layout.tsx carries no font <link>.
134
+ fonts: [
135
+ font({
136
+ family: "Inter",
137
+ variable: "--font-sans",
138
+ weights: ["400", "500", "600", "700"],
139
+ subsets: ["latin"],
140
+ display: "swap",
141
+ preload: true,
142
+ }),
143
+ ],
129
144
  routes: await discoverAppRoutes(),
130
145
  });
131
146
 
@@ -1,11 +1,12 @@
1
- // Who owns this waitlist? A waitlist is single-tenant — one business, one
2
- // owner — so ownership is just "the email the owner signs in with", configured
3
- // once via the PYLON_OWNER_EMAIL env var. The owner-only `waitlistStats`
4
- // function reads that env (via `ctx.env`) and compares it here.
1
+ // Who owns this shop? A shop is single-tenant — one business, one owner — so
2
+ // ownership is just "the email the owner signs in with", configured once via the
3
+ // PYLON_OWNER_EMAIL env var. The owner-only functions (ordersForOwner,
4
+ // fulfillOrder, cancelOrder, restockProduct) read that env (via `ctx.env`) and
5
+ // compare it here.
5
6
  //
6
7
  // Fail closed: if PYLON_OWNER_EMAIL is unset, NOBODY is the owner and the
7
8
  // dashboard stays locked. That's deliberate — an unset owner on a public site
8
- // must not mean "everyone can read the signups". Set it in .env (see
9
+ // must not mean "everyone can read the orders". Set it in .env (see
9
10
  // .env.example) before signing in.
10
11
 
11
12
  export function normalizeOwner(raw: string | null | undefined): string | null {
@@ -1,7 +1,7 @@
1
1
  import React from "react";
2
2
 
3
- // A layout wraps every page. This one is intentionally minimal — a header
4
- // and a centered column. The page below it is server-rendered first (so the
3
+ // A layout wraps every page. This one is intentionally minimal — just a
4
+ // centered column. The page below it is server-rendered first (so the
5
5
  // shell and copy are in the HTML), then hydrates into the live todo UI.
6
6
  interface LayoutProps {
7
7
  children: React.ReactNode;
@@ -14,7 +14,7 @@ export default async function sitemap(): Promise<Sitemap> {
14
14
  { url: `${SITE}/signup`, changeFrequency: "yearly", priority: 0.5 },
15
15
  ];
16
16
 
17
- // The export is async, so you can enumerate dynamic pages from a DB read:
17
+ // Example enumerate dynamic pages from a DB read:
18
18
  //
19
19
  // const posts = await fetchPublishedPosts();
20
20
  // const postRoutes: Sitemap = posts.map((p) => ({
@@ -7,11 +7,10 @@ import {
7
7
  discoverAppRoutes,
8
8
  } from "@pylonsync/sdk";
9
9
 
10
- // A todo that belongs to one person. `userId: field.owner()` is the key
11
- // move: the framework stamps the signed-in (here: guest) user's id
12
- // server-side on insert and rejects any forged value — so the UI can do a
13
- // plain, optimistic `db.insert("Todo", { title })` (the row shows instantly,
14
- // no round-trip) while ownership stays unspoofable. No createTodo function to
10
+ // A todo that belongs to one person. `userId: field.owner()` stamps the
11
+ // signed-in (here: guest) user's id server-side on insert and rejects any
12
+ // forged value — so the UI can do a plain, optimistic `db.insert("Todo",
13
+ // { title })` and never send (or spoof) userId. No createTodo function to
15
14
  // write — every verb is a direct, policy-checked entity call.
16
15
  const Todo = entity(
17
16
  "Todo",
@@ -4,18 +4,14 @@ import tailwindcss from "@tailwindcss/vite";
4
4
 
5
5
  // Pylon dev exposes TWO ports:
6
6
  // :4321 → HTTP (functions, entity CRUD, /api/sync/pull, /api/auth/*)
7
- // :4322 → dedicated WebSocket listener with TCP read timeouts set
8
- // on the raw socket. Broadcasts flow immediately because
9
- // the reader thread's mutex is released every 200ms by the
10
- // kernel-level read timeout — no client keepalive ping
11
- // needed to break the wedge.
7
+ // :4322 → dedicated WebSocket listener that pushes broadcasts with
8
+ // lower latency than the HTTP-multiplexed path.
12
9
  //
13
10
  // We route the WS upgrade to :4322 and everything else to :4321. The
14
- // HTTP-multiplexed `/api/sync/ws` on :4321 also works (and is the
11
+ // HTTP-multiplexed `/api/sync/ws` on :4321 also works and is the
15
12
  // production fallback for proxies that can't forward to a secondary
16
- // port), but it can't set stream-level timeouts because tiny_http's
17
- // `CustomStream` hides the underlying TcpStream — so broadcasts there
18
- // are latency-bounded by the client SDK's 200ms keepalive ping.
13
+ // port, but its broadcasts are latency-bounded by the client SDK's
14
+ // 200ms keepalive ping.
19
15
  const PYLON_HTTP_TARGET = process.env.PYLON_TARGET ?? "http://localhost:4321";
20
16
  const PYLON_WS_TARGET = process.env.PYLON_WS_TARGET ?? "ws://localhost:4322";
21
17
 
@@ -139,7 +139,14 @@
139
139
  body {
140
140
  background-color: var(--color-background);
141
141
  color: var(--color-foreground);
142
- font-family: Inter, ui-sans-serif, system-ui, -apple-system, sans-serif;
142
+ font-family: var(
143
+ --font-sans,
144
+ Inter,
145
+ ui-sans-serif,
146
+ system-ui,
147
+ -apple-system,
148
+ sans-serif
149
+ );
143
150
  -webkit-font-smoothing: antialiased;
144
151
  }
145
152
  button {
@@ -44,12 +44,10 @@ export default function RootLayout({ children, url, auth }: LayoutProps) {
44
44
  <meta charSet="utf-8" />
45
45
  <meta name="viewport" content="width=device-width, initial-scale=1" />
46
46
  {/* No <title> here — each page's exported `metadata` sets it. */}
47
- <link rel="preconnect" href="https://fonts.googleapis.com" />
48
- <link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="anonymous" />
49
- <link
50
- rel="stylesheet"
51
- href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap"
52
- />
47
+ {/* Inter is declared in app.ts (fonts: [...]) and self-hosted by the
48
+ build — the runtime injects @font-face + <link rel=preload> + a
49
+ size-adjusted fallback here automatically. No third-party request,
50
+ no layout shift; change the family in app.ts. */}
53
51
  {/* Tailwind is compiled by Pylon from app/globals.css and injected here. */}
54
52
  </head>
55
53
  <body className="flex min-h-screen flex-col bg-background text-foreground antialiased">
@@ -6,11 +6,10 @@ import { EnsureGuest } from "@pylonsync/client";
6
6
  import type { WaitlistConfig } from "@/lib/site.config";
7
7
 
8
8
  // The interactive top of the landing page: the email-capture form and the LIVE
9
- // signup counter. This is the realtime proof the counter is a live
10
- // `db.useQuery("WaitlistStat")` over the public, PII-free aggregate row, so the
11
- // moment anyone (this tab or another) submits an email, joinWaitlist updates
12
- // that row and the new count syncs to every open tab through the replica. No
13
- // refresh, no polling.
9
+ // signup counter. The counter is a live `db.useQuery("WaitlistStat")` over the
10
+ // public, PII-free aggregate row, so the moment anyone (this tab or another)
11
+ // submits an email, joinWaitlist updates that row and the new count syncs to
12
+ // every open tab through the replica. No refresh, no polling.
14
13
  //
15
14
  // The signup form (joinWaitlist) is a public mutation, so it works for any
16
15
  // anonymous visitor. The counter needs a live sync connection, so it's wrapped
@@ -5,20 +5,23 @@ import {
5
5
  auth,
6
6
  buildManifest,
7
7
  discoverAppRoutes,
8
+ font,
8
9
  } from "@pylonsync/sdk";
9
10
 
10
11
  // ---------------------------------------------------------------------------
11
12
  // waitlist — a pre-launch / coming-soon landing page with a LIVE signup
12
- // counter. The whole point is the realtime hook: open the page in two tabs,
13
- // submit an email in one, and the counter on the other ticks up with no
14
- // refresh. That's the proof it's a real live app and not a static page.
13
+ // counter. The realtime hook: open the page in two tabs, submit an email in
14
+ // one, and the counter on the other ticks up with no refresh.
15
15
  //
16
- // The data model is deliberately tiny — two entities:
17
- // • Signup — one row per email. Holds visitor PII, so it denies ALL client
18
- // reads/writes (writes go through the joinWaitlist mutation; the
19
- // public page only ever sees an aggregate count, never an email).
20
- // User — the business owner's account (email/password is built in), so
21
- // the owner can sign in to the dashboard and see their signups.
16
+ // The data model is deliberately tiny — three entities:
17
+ // • Signup — one row per email. Holds visitor PII, so it denies ALL
18
+ // client reads/writes (writes go through the joinWaitlist
19
+ // mutation; the public page only ever sees an aggregate
20
+ // count, never an email).
21
+ // WaitlistStat a single-row, PII-free aggregate (just the count) the
22
+ // public page reads live for the counter.
23
+ // • User — the business owner's account (email/password is built in),
24
+ // so the owner can sign in to the dashboard and see signups.
22
25
  // ---------------------------------------------------------------------------
23
26
 
24
27
  // One waitlist signup. `email` is the only PII; `createdAt` powers the
@@ -125,6 +128,20 @@ const manifest = buildManifest({
125
128
  // Email/password is on by default against the User entity above. No orgs,
126
129
  // no billing — a waitlist is single-tenant (one business, one owner).
127
130
  auth: auth(),
131
+ // Self-hosted Inter (next/font parity): the build fetches the woff2, serves it
132
+ // same-origin (no third-party request, no FOUT), preloads it, and synthesizes a
133
+ // size-adjusted fallback face so there's no layout shift. globals.css reads it
134
+ // via `var(--font-sans, …)`; layout.tsx carries no font <link>.
135
+ fonts: [
136
+ font({
137
+ family: "Inter",
138
+ variable: "--font-sans",
139
+ weights: ["400", "500", "600", "700"],
140
+ subsets: ["latin"],
141
+ display: "swap",
142
+ preload: true,
143
+ }),
144
+ ],
128
145
  routes: await discoverAppRoutes(),
129
146
  });
130
147
 
@@ -1,11 +1,9 @@
1
1
  // THE single source of truth for everything business-specific on this waitlist.
2
2
  // Rebrand the whole page by editing this ONE file — the landing page and layout
3
- // read from here and stay generic. The `create-pylon` scaffolder and automated
4
- // generators (Mast) target this file too, so a whole site can be themed by
5
- // producing one typed object.
3
+ // read from here and stay generic.
6
4
  //
7
- // Colors live here (applied as CSS variables on <html> in app/layout.tsx), so
8
- // you don't touch globals.css to re-theme.
5
+ // Colors live here too (applied as CSS variables on <html> in app/layout.tsx),
6
+ // so you don't touch globals.css to re-theme.
9
7
  //
10
8
  // Fictional demo copy — replace the values, keep the shape.
11
9
 
@@ -1,4 +1,3 @@
1
- /** Tailwind v4 PostCSS pipeline. */
2
1
  export default {
3
2
  plugins: { "@tailwindcss/postcss": {} },
4
3
  };
@@ -1,9 +1,6 @@
1
1
  @import "tailwindcss";
2
2
  @source "../../../../packages/ui/src/**/*.{ts,tsx}";
3
3
 
4
- :root {
5
- color-scheme: light dark;
6
- }
7
-
4
+ :root { color-scheme: light dark; }
8
5
  html, body { height: 100%; }
9
6
  body { font-family: ui-sans-serif, system-ui, -apple-system, sans-serif; }