create-smoodly-app 0.0.50 → 0.0.52

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 (159) hide show
  1. package/README.md +22 -30
  2. package/dist/index.js +87 -329
  3. package/package.json +4 -5
  4. package/templates/astro-blank/AGENTS.md +2 -8
  5. package/templates/astro-blank/README.md +13 -23
  6. package/templates/astro-blank/package.json +2 -3
  7. package/templates/astro-blank/scripts/seed.ts +9 -3
  8. package/templates/astro-blank/src/smoodly/collections.ts +2 -2
  9. package/templates/astro-blank-intl/AGENTS.md +2 -8
  10. package/templates/astro-blank-intl/README.md +13 -23
  11. package/templates/astro-blank-intl/package.json +2 -3
  12. package/templates/astro-blank-intl/scripts/seed.ts +9 -3
  13. package/templates/astro-blank-intl/src/smoodly/collections.ts +2 -2
  14. package/templates/astro-lorem/AGENTS.md +2 -8
  15. package/templates/astro-lorem/README.md +12 -16
  16. package/templates/astro-lorem/package.json +2 -3
  17. package/templates/astro-lorem/scripts/seed.ts +9 -3
  18. package/templates/astro-lorem/src/components/sections/PageHero.schema.ts +3 -1
  19. package/templates/astro-lorem/src/sections/pageHero.astro +1 -1
  20. package/templates/astro-lorem-intl/AGENTS.md +2 -8
  21. package/templates/astro-lorem-intl/README.md +12 -16
  22. package/templates/astro-lorem-intl/package.json +2 -3
  23. package/templates/astro-lorem-intl/scripts/seed.ts +9 -3
  24. package/templates/astro-lorem-intl/src/components/sections/PageHero.schema.ts +3 -1
  25. package/templates/astro-lorem-intl/src/sections/pageHero.astro +1 -1
  26. package/templates/blank/AGENTS.md +7 -13
  27. package/templates/blank/README.md +12 -22
  28. package/templates/blank/app/(smoodly)/admin/[[...segments]]/route.ts +6 -5
  29. package/templates/blank/next.config.ts +1 -4
  30. package/templates/blank/package.json +4 -4
  31. package/templates/blank/scripts/seed.ts +9 -3
  32. package/templates/blank/smoodly/collections.ts +2 -2
  33. package/templates/blank/smoodly/server.ts +25 -47
  34. package/templates/blank-intl/AGENTS.md +7 -13
  35. package/templates/blank-intl/README.md +12 -22
  36. package/templates/blank-intl/app/(smoodly)/admin/[[...segments]]/route.ts +6 -5
  37. package/templates/blank-intl/next.config.ts +1 -4
  38. package/templates/blank-intl/package.json +4 -4
  39. package/templates/blank-intl/scripts/seed.ts +9 -3
  40. package/templates/blank-intl/smoodly/collections.ts +2 -2
  41. package/templates/blank-intl/smoodly/server.ts +25 -47
  42. package/templates/blank-intl.tsconfig.json +0 -3
  43. package/templates/blank.tsconfig.json +0 -3
  44. package/templates/lorem/AGENTS.md +7 -13
  45. package/templates/lorem/README.md +13 -17
  46. package/templates/lorem/app/(smoodly)/admin/[[...segments]]/route.ts +6 -5
  47. package/templates/lorem/components/sections/PageHero.schema.ts +3 -1
  48. package/templates/lorem/components/sections/PageHero.tsx +1 -1
  49. package/templates/lorem/next.config.ts +1 -4
  50. package/templates/lorem/package.json +4 -4
  51. package/templates/lorem/scripts/seed.ts +9 -3
  52. package/templates/lorem/smoodly/server.ts +25 -47
  53. package/templates/lorem-intl/AGENTS.md +7 -13
  54. package/templates/lorem-intl/README.md +13 -17
  55. package/templates/lorem-intl/app/(smoodly)/admin/[[...segments]]/route.ts +6 -5
  56. package/templates/lorem-intl/components/sections/PageHero.schema.ts +3 -1
  57. package/templates/lorem-intl/components/sections/PageHero.tsx +1 -1
  58. package/templates/lorem-intl/next.config.ts +1 -4
  59. package/templates/lorem-intl/package.json +4 -4
  60. package/templates/lorem-intl/scripts/seed.ts +9 -3
  61. package/templates/lorem-intl/smoodly/server.ts +25 -47
  62. package/templates/lorem-intl.tsconfig.json +0 -3
  63. package/templates/lorem.tsconfig.json +0 -3
  64. package/templates/astro-blank/supabase/migrations/20260926120000_smoodly_v1.sql +0 -535
  65. package/templates/astro-blank/supabase/migrations/20260926130000_smoodly_v2.sql +0 -26
  66. package/templates/astro-blank/supabase/migrations/20260926140000_smoodly_v3.sql +0 -38
  67. package/templates/astro-blank/supabase/migrations/20260926150000_smoodly_v4.sql +0 -41
  68. package/templates/astro-blank/supabase/migrations/20260929120000_smoodly_v5.sql +0 -56
  69. package/templates/astro-blank/supabase/migrations/20260929135523_smoodly_v6.sql +0 -200
  70. package/templates/astro-blank/supabase/migrations/20260930040956_smoodly_v7.sql +0 -84
  71. package/templates/astro-blank/supabase/migrations/20261007120000_smoodly_v8.sql +0 -154
  72. package/templates/astro-blank/supabase/migrations/20261007200000_smoodly_v9.sql +0 -30
  73. package/templates/astro-blank/supabase/migrations/20261008120000_smoodly_v10.sql +0 -47
  74. package/templates/astro-blank/supabase/migrations/20261009120000_smoodly_v11.sql +0 -49
  75. package/templates/astro-blank/supabase/migrations/20261009120100_smoodly_views.sql +0 -20
  76. package/templates/astro-blank-intl/supabase/migrations/20260926120000_smoodly_v1.sql +0 -535
  77. package/templates/astro-blank-intl/supabase/migrations/20260926130000_smoodly_v2.sql +0 -26
  78. package/templates/astro-blank-intl/supabase/migrations/20260926140000_smoodly_v3.sql +0 -38
  79. package/templates/astro-blank-intl/supabase/migrations/20260926150000_smoodly_v4.sql +0 -41
  80. package/templates/astro-blank-intl/supabase/migrations/20260929120000_smoodly_v5.sql +0 -56
  81. package/templates/astro-blank-intl/supabase/migrations/20260929135523_smoodly_v6.sql +0 -200
  82. package/templates/astro-blank-intl/supabase/migrations/20260930040956_smoodly_v7.sql +0 -84
  83. package/templates/astro-blank-intl/supabase/migrations/20261007120000_smoodly_v8.sql +0 -154
  84. package/templates/astro-blank-intl/supabase/migrations/20261007200000_smoodly_v9.sql +0 -30
  85. package/templates/astro-blank-intl/supabase/migrations/20261008120000_smoodly_v10.sql +0 -47
  86. package/templates/astro-blank-intl/supabase/migrations/20261009120000_smoodly_v11.sql +0 -49
  87. package/templates/astro-blank-intl/supabase/migrations/20261009120100_smoodly_views.sql +0 -20
  88. package/templates/astro-lorem/supabase/migrations/20260926120000_smoodly_v1.sql +0 -535
  89. package/templates/astro-lorem/supabase/migrations/20260926130000_smoodly_v2.sql +0 -26
  90. package/templates/astro-lorem/supabase/migrations/20260926140000_smoodly_v3.sql +0 -38
  91. package/templates/astro-lorem/supabase/migrations/20260926150000_smoodly_v4.sql +0 -41
  92. package/templates/astro-lorem/supabase/migrations/20260929120000_smoodly_v5.sql +0 -56
  93. package/templates/astro-lorem/supabase/migrations/20260929135523_smoodly_v6.sql +0 -200
  94. package/templates/astro-lorem/supabase/migrations/20260930040956_smoodly_v7.sql +0 -84
  95. package/templates/astro-lorem/supabase/migrations/20261007120000_smoodly_v8.sql +0 -154
  96. package/templates/astro-lorem/supabase/migrations/20261007200000_smoodly_v9.sql +0 -30
  97. package/templates/astro-lorem/supabase/migrations/20261008120000_smoodly_v10.sql +0 -47
  98. package/templates/astro-lorem/supabase/migrations/20261009120000_smoodly_v11.sql +0 -49
  99. package/templates/astro-lorem/supabase/migrations/20261009120100_smoodly_views.sql +0 -58
  100. package/templates/astro-lorem-intl/supabase/migrations/20260926120000_smoodly_v1.sql +0 -535
  101. package/templates/astro-lorem-intl/supabase/migrations/20260926130000_smoodly_v2.sql +0 -26
  102. package/templates/astro-lorem-intl/supabase/migrations/20260926140000_smoodly_v3.sql +0 -38
  103. package/templates/astro-lorem-intl/supabase/migrations/20260926150000_smoodly_v4.sql +0 -41
  104. package/templates/astro-lorem-intl/supabase/migrations/20260929120000_smoodly_v5.sql +0 -56
  105. package/templates/astro-lorem-intl/supabase/migrations/20260929135523_smoodly_v6.sql +0 -200
  106. package/templates/astro-lorem-intl/supabase/migrations/20260930040956_smoodly_v7.sql +0 -84
  107. package/templates/astro-lorem-intl/supabase/migrations/20261007120000_smoodly_v8.sql +0 -154
  108. package/templates/astro-lorem-intl/supabase/migrations/20261007200000_smoodly_v9.sql +0 -30
  109. package/templates/astro-lorem-intl/supabase/migrations/20261008120000_smoodly_v10.sql +0 -47
  110. package/templates/astro-lorem-intl/supabase/migrations/20261009120000_smoodly_v11.sql +0 -49
  111. package/templates/astro-lorem-intl/supabase/migrations/20261009120100_smoodly_views.sql +0 -58
  112. package/templates/blank/supabase/migrations/20260926120000_smoodly_v1.sql +0 -535
  113. package/templates/blank/supabase/migrations/20260926130000_smoodly_v2.sql +0 -26
  114. package/templates/blank/supabase/migrations/20260926140000_smoodly_v3.sql +0 -38
  115. package/templates/blank/supabase/migrations/20260926150000_smoodly_v4.sql +0 -41
  116. package/templates/blank/supabase/migrations/20260929120000_smoodly_v5.sql +0 -56
  117. package/templates/blank/supabase/migrations/20260929135523_smoodly_v6.sql +0 -200
  118. package/templates/blank/supabase/migrations/20260930040956_smoodly_v7.sql +0 -84
  119. package/templates/blank/supabase/migrations/20261007120000_smoodly_v8.sql +0 -154
  120. package/templates/blank/supabase/migrations/20261007200000_smoodly_v9.sql +0 -30
  121. package/templates/blank/supabase/migrations/20261008120000_smoodly_v10.sql +0 -47
  122. package/templates/blank/supabase/migrations/20261009120000_smoodly_v11.sql +0 -49
  123. package/templates/blank/supabase/migrations/20261009120100_smoodly_views.sql +0 -20
  124. package/templates/blank-intl/supabase/migrations/20260926120000_smoodly_v1.sql +0 -535
  125. package/templates/blank-intl/supabase/migrations/20260926130000_smoodly_v2.sql +0 -26
  126. package/templates/blank-intl/supabase/migrations/20260926140000_smoodly_v3.sql +0 -38
  127. package/templates/blank-intl/supabase/migrations/20260926150000_smoodly_v4.sql +0 -41
  128. package/templates/blank-intl/supabase/migrations/20260929120000_smoodly_v5.sql +0 -56
  129. package/templates/blank-intl/supabase/migrations/20260929135523_smoodly_v6.sql +0 -200
  130. package/templates/blank-intl/supabase/migrations/20260930040956_smoodly_v7.sql +0 -84
  131. package/templates/blank-intl/supabase/migrations/20261007120000_smoodly_v8.sql +0 -154
  132. package/templates/blank-intl/supabase/migrations/20261007200000_smoodly_v9.sql +0 -30
  133. package/templates/blank-intl/supabase/migrations/20261008120000_smoodly_v10.sql +0 -47
  134. package/templates/blank-intl/supabase/migrations/20261009120000_smoodly_v11.sql +0 -49
  135. package/templates/blank-intl/supabase/migrations/20261009120100_smoodly_views.sql +0 -20
  136. package/templates/lorem/supabase/migrations/20260926120000_smoodly_v1.sql +0 -535
  137. package/templates/lorem/supabase/migrations/20260926130000_smoodly_v2.sql +0 -26
  138. package/templates/lorem/supabase/migrations/20260926140000_smoodly_v3.sql +0 -38
  139. package/templates/lorem/supabase/migrations/20260926150000_smoodly_v4.sql +0 -41
  140. package/templates/lorem/supabase/migrations/20260929120000_smoodly_v5.sql +0 -56
  141. package/templates/lorem/supabase/migrations/20260929135523_smoodly_v6.sql +0 -200
  142. package/templates/lorem/supabase/migrations/20260930040956_smoodly_v7.sql +0 -84
  143. package/templates/lorem/supabase/migrations/20261007120000_smoodly_v8.sql +0 -154
  144. package/templates/lorem/supabase/migrations/20261007200000_smoodly_v9.sql +0 -30
  145. package/templates/lorem/supabase/migrations/20261008120000_smoodly_v10.sql +0 -47
  146. package/templates/lorem/supabase/migrations/20261009120000_smoodly_v11.sql +0 -49
  147. package/templates/lorem/supabase/migrations/20261009120100_smoodly_views.sql +0 -58
  148. package/templates/lorem-intl/supabase/migrations/20260926120000_smoodly_v1.sql +0 -535
  149. package/templates/lorem-intl/supabase/migrations/20260926130000_smoodly_v2.sql +0 -26
  150. package/templates/lorem-intl/supabase/migrations/20260926140000_smoodly_v3.sql +0 -38
  151. package/templates/lorem-intl/supabase/migrations/20260926150000_smoodly_v4.sql +0 -41
  152. package/templates/lorem-intl/supabase/migrations/20260929120000_smoodly_v5.sql +0 -56
  153. package/templates/lorem-intl/supabase/migrations/20260929135523_smoodly_v6.sql +0 -200
  154. package/templates/lorem-intl/supabase/migrations/20260930040956_smoodly_v7.sql +0 -84
  155. package/templates/lorem-intl/supabase/migrations/20261007120000_smoodly_v8.sql +0 -154
  156. package/templates/lorem-intl/supabase/migrations/20261007200000_smoodly_v9.sql +0 -30
  157. package/templates/lorem-intl/supabase/migrations/20261008120000_smoodly_v10.sql +0 -47
  158. package/templates/lorem-intl/supabase/migrations/20261009120000_smoodly_v11.sql +0 -49
  159. package/templates/lorem-intl/supabase/migrations/20261009120100_smoodly_views.sql +0 -58
@@ -1,60 +1,47 @@
1
- // Server-only glue: the client, the stores, the site layer and the
2
- // renderer, constructed lazily so `next build` doesn't need env vars at
3
- // import time. The env shape is the package's (SMOODLY_*): the live site
4
- // needs SMOODLY_URL and SMOODLY_SECRET_KEY, the admin also
5
- // SMOODLY_PUBLISHABLE_KEY (the `smoodlyEnv("admin")` calls below); whether the
6
- // secret is a service-role key or a cloud write key is invisible here.
1
+ // Server-only glue: the reads over Smoodly's API and the renderer,
2
+ // constructed lazily so `next build` doesn't need env vars at import time.
3
+ // The env shape is the package's (SMOODLY_*): the live site and the admin need
4
+ // SMOODLY_API_URL, SMOODLY_SPACE_ID and SMOODLY_SECRET_KEY; the admin learns
5
+ // where editors sign in from Smoodly's API. The site opens no database.
7
6
  //
8
7
  // Lazy on purpose: a page component imports this file to read through
9
8
  // `q` (components/pages/Articles.tsx), and this file imports the
10
9
  // components through ./views. That is a cycle, so nothing here reads
11
10
  // views() or the env at module scope.
12
- import {
13
- createSmoodlyClient, createSmoodlySite, createSupabaseStores, presenceConfig, siteOptions, smoodlyEnv,
14
- type PresenceConfig, type SmoodlySite, type SupabaseAssetStore, type SupabaseEntryStore, type SupabaseMemberStore, type SupabasePageStore,
15
- } from "smoodly";
16
- // The admin's server pieces: the sign-in check and the content API. No browser code lives in the package's
17
- // entries; the admin itself is prebuilt and served by app/(smoodly)/admin/[[...segments]]/route.ts.
18
- import { createSmoodlyContent, supabaseAdminAuth, type AdminAuth, type AuthConfig, type SmoodlyContent } from "smoodly/admin";
11
+ import { apiEnv, createSmoodlyApi, presenceConfig, smoodlyEnv, type ContentReads, type PresenceConfig } from "smoodly";
12
+ // The admin's server pieces: the content API for scripts. No browser code lives in the package's entries;
13
+ // the admin itself is prebuilt and served by app/(smoodly)/admin/[[...segments]]/route.ts.
14
+ import { apiTransport, createSmoodlyContent, type SmoodlyContent } from "smoodly/admin";
19
15
  import { buildGate, createPageRenderer, createSmoodlyQueries } from "smoodly/next";
20
- import type { SupabaseClient } from "@supabase/supabase-js";
21
16
  import { getPathname } from "@/i18n/navigation";
22
17
  import { collections, config, globals } from "./config";
23
18
  import { views } from "./views";
24
19
 
25
20
  type Renderer = ReturnType<typeof createPageRenderer>;
26
21
  type Queries = ReturnType<typeof createSmoodlyQueries<typeof collections, typeof globals>>;
27
- type Bound = {
28
- db: SupabaseClient; pages: SupabasePageStore; entries: SupabaseEntryStore; assets: SupabaseAssetStore; members: SupabaseMemberStore; auth: AdminAuth; site: SmoodlySite; renderer: Renderer; q: Queries;
29
- };
22
+ type Bound = { site: ContentReads; renderer: Renderer; q: Queries };
30
23
 
31
24
  let cached: Bound | null = null;
32
25
 
33
26
  function bound(): Bound {
34
27
  if (cached) return cached;
35
- const env = smoodlyEnv();
36
- const db = createSmoodlyClient(env);
37
- // The stores, read off the config: pages, entries, the media library and the admission table.
38
- const { pages, entries, assets, members, published } = createSupabaseStores(db, config);
39
- const auth = supabaseAdminAuth({ authUrl: env.authUrl, authKey: env.authKey, db });
40
- // The site layer: (locale, path) → page or entry, and the content API
41
- // `q` wraps below. Not exported — the app reads through `q`, which adds
42
- // draft awareness and the cache.
43
- const site = createSmoodlySite(siteOptions(config, () => ({ pages, entries, assets, published })));
28
+ // The site's reads go over Smoodly's API (SMOODLY_API_URL, SMOODLY_SPACE_ID, SMOODLY_SECRET_KEY):
29
+ // (locale, path) → page or entry, and the reads `q` wraps below. Not exported — the app reads
30
+ // through `q`, which adds draft awareness and the cache.
31
+ const site = createSmoodlyApi({ ...apiEnv(), config });
44
32
  // The components, checked against the config's schemas here, on first use.
45
33
  const renderer = createPageRenderer({ site, ...views(), href });
46
34
  // The read API (spec 2026-09-11): typed by the registry, draft-aware, cached under tags the admin expires.
47
35
  const q = createSmoodlyQueries({ site, collections, globals });
48
- cached = { db, pages, entries, assets, members, auth, site, renderer, q };
36
+ cached = { site, renderer, q };
49
37
  return cached;
50
38
  }
51
39
 
52
- /** The admin's wiring, for the admin route only. Scripts write through `content()`. */
40
+ /** The admin's wiring, for the admin route only: the page and its files, the revalidate route the
41
+ * service calls and the preview route the browser posts the grant to. Operations run in the service. */
53
42
  export function adminDeps() {
54
- smoodlyEnv("admin"); // the admin signs in with the publishable key: a missing one is named here, not on a live page
55
- const { pages, entries, assets, members, auth, site } = bound();
56
- // The projector: a publish writes the finished documents a live page reads.
57
- return { pages, entries, assets, members, auth, href, projector: site.projector };
43
+ const env = smoodlyEnv("admin"); // the admin talks to SMOODLY_API_URL, which also tells it where editors sign in
44
+ return { apiUrl: env.apiUrl, spaceId: env.spaceId, delivery: "live" as const };
58
45
  }
59
46
 
60
47
  /** Write content from code — seeds, migrations, route handlers (spec 2026-09-30 tester findings §1).
@@ -62,24 +49,15 @@ export function adminDeps() {
62
49
  * throws `SmoodlyContentError`. `content().pages.create(…)`, `.entries.create(…)`, `.globals.set(…)`,
63
50
  * `.media.upload(…)`; build tree nodes with `node(Section, fields)`. */
64
51
  export function content(): SmoodlyContent {
65
- const { pages, entries, assets, members, site } = bound();
66
- return createSmoodlyContent({ config, pages, entries, assets, members, href, projector: site.projector });
52
+ // Over Smoodly's API with the secret key: the service runs the operations.
53
+ return createSmoodlyContent({ config, href, transport: apiTransport(apiEnv()) });
67
54
  }
68
55
 
69
56
  export { node } from "smoodly";
70
57
 
71
- /** The server-side client (seed script, one-off admin tasks). */
72
- export const client = () => bound().db;
73
-
74
- /** What the browser needs to sign in — public values only. */
75
- export function publicAuth(): AuthConfig {
76
- const env = smoodlyEnv("admin");
77
- return { url: env.authUrl, key: env.authKey };
78
- }
79
-
80
- /** What the browser needs to join the editing-presence channel — public values only (spec 2026-09-26 concurrency §5).
81
- * The space is asked of the database once per process. */
82
- export function presence(): Promise<PresenceConfig> {
58
+ /** What the browser needs to open the editing-presence socket — Smoodly's API and the space, public values only
59
+ * (spec 2026-10-15 the space object §6). */
60
+ export function presence(): PresenceConfig {
83
61
  return presenceConfig(smoodlyEnv("admin"));
84
62
  }
85
63
 
@@ -106,7 +84,7 @@ export const q = (): Queries =>
106
84
  // route file without a dynamic segment reads nothing at build and the build needs no keys — a layout
107
85
  // reading a global through q() included (2026-10-02, tester round 6). At run time it does nothing: a
108
86
  // CMS route renders on its first visit and is cached until a publish expires it (plan 2026-10-09).
109
- /** The two route files call these. */
87
+ /** The route file calls these. */
110
88
  export const renderSmoodlyPath: Renderer["renderSmoodlyPath"] = async (...args) => { await buildGate(); return bound().renderer.renderSmoodlyPath(...args); };
111
89
  export const smoodlyMetadata: Renderer["smoodlyMetadata"] = async (...args) => { await buildGate(); return bound().renderer.smoodlyMetadata(...args); };
112
90
  export const renderSmoodlyPage: Renderer["renderSmoodlyPage"] = async (...args) => { await buildGate(); return bound().renderer.renderSmoodlyPage(...args); };
@@ -10,9 +10,6 @@
10
10
  "smoodly": [
11
11
  "../../smoodly/src/index.ts"
12
12
  ],
13
- "smoodly/image/config": [
14
- "../../smoodly/src/image/next-config.ts"
15
- ],
16
13
  "smoodly/*": [
17
14
  "../../smoodly/src/*"
18
15
  ]
@@ -10,9 +10,6 @@
10
10
  "smoodly": [
11
11
  "../../smoodly/src/index.ts"
12
12
  ],
13
- "smoodly/image/config": [
14
- "../../smoodly/src/image/next-config.ts"
15
- ],
16
13
  "smoodly/*": [
17
14
  "../../smoodly/src/*"
18
15
  ]
@@ -1,9 +1,9 @@
1
1
  # Working in this Smoodly site
2
2
 
3
- This is a Next.js app with Smoodly installed: content lives in Supabase,
4
- the admin is mounted at /admin from the `smoodly` package, and the site
5
- reads content through `smoodly/server.ts`. Read README.md first: it says
6
- where things are.
3
+ This is a Next.js app with Smoodly installed: content lives in Smoodly's service,
4
+ reached through the API, the admin is mounted at /admin from the `smoodly`
5
+ package, and the site reads content through `smoodly/server.ts`. Read
6
+ README.md first: it says where things are.
7
7
 
8
8
  ## Rules
9
9
 
@@ -20,12 +20,6 @@ where things are.
20
20
  `smoodly/server.ts` or `smoodly/richtext.tsx`. Zones, toolsets, global
21
21
  items and `node()` take schemas (`z.locked(heroSchema)`), never
22
22
  components.
23
- - **After a new collection or a `smoodly` upgrade, run `npx smoodly migrate`**
24
- and apply what it writes (`supabase db push` hosted, `supabase migration up`
25
- local). It copies the core schema files the package ships and writes a
26
- read view and order index per collection. Never edit a file it wrote.
27
- The admin reads the base tables, so it lists a collection either way;
28
- the view is for SQL and PostgREST readers, the index for `order`.
29
23
  - **Content from code goes through `content()`.** `scripts/seed.ts` is
30
24
  starter content written with `content()` from `smoodly/server.ts`:
31
25
  the admin's own operations, so validation and publish checks apply and
@@ -35,8 +29,8 @@ where things are.
35
29
  serves the admin's page, its files and its operations from the
36
30
  `smoodly` package; the folder name must match `admin.path` in the
37
31
  config. The admin is prebuilt: nothing of it is compiled into the
38
- site, and it takes no layout. Server-side pieces such as
39
- `supabaseAdminAuth` and the content API come from `smoodly/admin`.
32
+ site, and it takes no layout. The content API (`createSmoodlyContent`,
33
+ `apiTransport`) comes from `smoodly/admin`.
40
34
  - **One root layout.** The site's layout lives in its route group or
41
35
  locale segment. Do not add an `app/layout.tsx`: it would wrap that
42
36
  layout in a second `<html>`.
@@ -59,6 +53,6 @@ where things are.
59
53
  ## Commands
60
54
 
61
55
  npm run dev # http://localhost:3000, /admin is the editor
62
- npm run build # checks the schema files (smoodly migrate --check), then next build
56
+ npm run build # next build
63
57
  npm run seed # starter content, idempotent
64
58
  npm run typecheck # tsc --noEmit
@@ -1,8 +1,8 @@
1
1
  # Smoodly site
2
2
 
3
- Created by `create-smoodly-app`. Content lives in Supabase; the admin is
4
- mounted at `/admin` from the `smoodly` package. AGENTS.md holds the rules
5
- for an agent (or a person) building the site.
3
+ Created by `create-smoodly-app`. Content lives in Smoodly's service; the
4
+ admin is mounted at `/admin` from the `smoodly` package. AGENTS.md holds
5
+ the rules for an agent (or a person) building the site.
6
6
 
7
7
  npm run dev # http://localhost:3000 — /admin is the editor
8
8
 
@@ -16,9 +16,9 @@ for an agent (or a person) building the site.
16
16
  - `components/strings.ts`, `messages/` — the site's own UI strings and the request locale, one seam.
17
17
  - `public/images/` — the starter's photos; image fields hold URLs like `/images/hero_image.jpg`.
18
18
  - `scripts/seed.ts` — the starter content, written through the content API (`content()`). `npm run seed` is idempotent.
19
- - `smoodly/collections.ts` — structured content (people, articles, news). Add a collection, run `npx smoodly migrate`, apply the file it writes.
19
+ - `smoodly/collections.ts` — structured content (people, articles, news). Add a collection to the list; nothing else to apply.
20
20
  - `smoodly/` — the wiring: `config.ts` (the registry of schemas and the settings; it imports no component), `collections.ts`, `sections.ts` and `elements.ts` (the schemas the config registers), `views.ts` (the components that render them), `server.ts` (site layer, renderer, the content API `content()`).
21
- - `.env.local` — `SMOODLY_URL`, `SMOODLY_SECRET_KEY`, `SMOODLY_PUBLISHABLE_KEY`.
21
+ - `.env.local` — `SMOODLY_SPACE_ID`, `SMOODLY_SECRET_KEY`, `SMOODLY_PUBLISHABLE_KEY` (the space's values from smoodly.io) and `SITE_URL`.
22
22
 
23
23
  ## What is in the starter
24
24
 
@@ -31,7 +31,7 @@ The Lorem Co. site the installer seeded is a tour of the features:
31
31
  - **An article** — a collection entry with a richtext body, tags (a localized list), a date and an author ref; versioned, so every save is a snapshot.
32
32
  - **Global content** — the header every page renders through `components/PageFrame.tsx`, the footer the layout renders, the contact details the footer reads, the quote pages place.
33
33
 
34
- Reseed an empty database any time with `npm run seed`.
34
+ Reseed an empty space any time with `npm run seed`.
35
35
 
36
36
  ## Moving the admin
37
37
 
@@ -44,16 +44,12 @@ The two must agree; the admin says so when they do not.
44
44
 
45
45
  ## Editors
46
46
 
47
- Anyone who may open `/admin` is a Supabase Auth user with a row in the
48
- `editors` table. To add one: create the user in Supabase Studio
49
- (Authentication → Users), then in the SQL editor:
47
+ Editors sign in at `/admin` with their smoodly.io account. Colleagues are
48
+ invited from the project at smoodly.io; the admin's Members page lists them.
50
49
 
51
- insert into editors (user_id, role) values ('<the user id>', 'editor');
50
+ ## Deploying
52
51
 
53
- ## Hosted Supabase
54
-
55
- supabase init # if the CLI has not been set up here yet
56
- supabase link # pick the project
57
- supabase db push # applies supabase/migrations/
58
-
59
- Then put the project's URL and keys in `.env.local`.
52
+ `.env.local` holds the preview space's values, for development. Where you
53
+ deploy, set the production space's three values and `SITE_URL` in the
54
+ host's environment variables; each build pushes the
55
+ schema to its space. https://smoodly.io/docs/getting-started/deploying
@@ -1,8 +1,9 @@
1
- // The admin: its page, the package's files and the operations endpoint, all from this one route. The
2
- // admin is prebuilt in the `smoodly` package; nothing of it is compiled into the site. The folder name
3
- // must match `admin.path` in smoodly/config.ts.
1
+ // The admin: its page, the package's files, the revalidate route Smoodly's API calls after a publish and
2
+ // the preview route, all from this one route; the operations run in Smoodly's API. The admin is prebuilt
3
+ // in the `smoodly` package; nothing of it is compiled into the site. The folder name must match
4
+ // `admin.path` in smoodly/config.ts.
4
5
  import { createAdminRoute } from "smoodly/next";
5
6
  import { config } from "@/smoodly/config";
6
- import { adminDeps, presence, publicAuth } from "@/smoodly/server";
7
+ import { adminDeps, presence } from "@/smoodly/server";
7
8
 
8
- export const { GET, POST } = createAdminRoute(() => ({ config, ...adminDeps(), signIn: publicAuth(), presence }));
9
+ export const { GET, POST } = createAdminRoute(() => ({ config, ...adminDeps(), presence }));
@@ -4,6 +4,8 @@ import { f, smoodly } from "smoodly";
4
4
  // text and an optional wide photo. Page and Articles lock it — each page
5
5
  // shape locks its own hero, which is the point: a locked zone is per
6
6
  // shape. Without an image the copy stands alone (the Articles index).
7
+ // The photo has two named crops: wide on a desktop, tall on a phone; the
8
+ // view says which applies where.
7
9
  export const pageHeroSchema = smoodly.schema.section({
8
10
  name: "pageHero",
9
11
  title: "Page hero",
@@ -12,7 +14,7 @@ export const pageHeroSchema = smoodly.schema.section({
12
14
  kicker: f.text().optional(),
13
15
  heading: f.text(),
14
16
  text: f.text(),
15
- image: f.image({ aspect: "21:9" }).optional(),
17
+ image: f.image({ crops: { wide: "21:9", tall: "4:5" } }).optional(),
16
18
  },
17
19
  sample: {
18
20
  heading: "About us",
@@ -13,7 +13,7 @@ function PageHeroView({ kicker, heading, text, image, styles }: Props<typeof pag
13
13
  <h1 className="page-hero__heading" {...m.field("heading")}>{heading}</h1>
14
14
  <p className="page-hero__text" {...m.field("text")}>{text}</p>
15
15
  </div>
16
- {image && <SmoodlyImage image={image} className="page-hero__image" sizes="100vw" priority {...m.field("image")} />}
16
+ {image && <SmoodlyImage image={image} media={{ tall: "(max-width: 767px)" }} className="page-hero__image" sizes="100vw" priority {...m.field("image")} />}
17
17
  </div>
18
18
  </smoodly.Section>
19
19
  );
@@ -1,8 +1,5 @@
1
1
  import type { NextConfig } from "next";
2
- import { smoodlyImages } from "smoodly/image/config";
3
2
 
4
- // next/image may only optimize from the Supabase host named in SMOODLY_URL
5
- // (a local stack also needs Next's local-IP allowance; the helper sets it).
6
- const config: NextConfig = { images: smoodlyImages() };
3
+ const config: NextConfig = {};
7
4
 
8
5
  export default config;
@@ -4,17 +4,17 @@
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "dev": "next dev",
7
- "build": "smoodly migrate --check && next build",
7
+ "build": "next build",
8
8
  "start": "next start",
9
9
  "seed": "tsx scripts/seed.ts",
10
- "typecheck": "tsc --noEmit"
10
+ "typecheck": "tsc --noEmit",
11
+ "prebuild": "smoodly push"
11
12
  },
12
13
  "dependencies": {
13
- "@supabase/supabase-js": "^2.113.0",
14
14
  "next": "^16.3.4",
15
15
  "react": "^19",
16
16
  "react-dom": "^19",
17
- "smoodly": "0.0.50"
17
+ "smoodly": "0.0.52"
18
18
  },
19
19
  "devDependencies": {
20
20
  "@next/env": "^16.3.4",
@@ -2,9 +2,9 @@
2
2
  // from smoodly/server.ts): the admin's own operations, so validation and
3
3
  // the publish checks apply and a failure throws. This file is also the
4
4
  // example of writing content from code. Content only; it ships in the template and create-smoodly-app runs
5
- // `npm run seed` after the first editor. (The example repo's demo
5
+ // `npm run seed` after it pushes the schema. (The example repo's demo
6
6
  // editor account is a separate, example-only script.)
7
- // Run: npm run seed (needs .env.local and a Supabase stack).
7
+ // Run: npm run seed (needs .env.local).
8
8
  //
9
9
  // Locales: the English content goes into the configured default locale,
10
10
  // whatever its code, and the Finnish rows are written only when `fi` is
@@ -21,6 +21,12 @@ const { loadEnvConfig } = next;
21
21
 
22
22
  loadEnvConfig(process.cwd());
23
23
 
24
+ // A refusal from Smoodly's API (a space with no schema yet, a wrong key) prints its message and code, not a stack trace.
25
+ process.on("uncaughtException", (e) => {
26
+ console.error(e.name === "SmoodlyContentError" ? `${e.message} (${(e as Error & { code?: string }).code})` : e);
27
+ process.exit(1);
28
+ });
29
+
24
30
  const { config } = await import("../smoodly/config");
25
31
  const { articles, news } = await import("../smoodly/collections");
26
32
  const { content, href, node } = await import("../smoodly/server");
@@ -36,7 +42,7 @@ const EN = config.locales.default;
36
42
  const FI = config.locales.supported.includes("fi") && EN !== "fi" ? "fi" : null;
37
43
 
38
44
  // The home page is code-owned: `find` returns its record, creating it when
39
- // the database is new. It is seeded once somebody has given it sections.
45
+ // the space is new. It is seeded once somebody has given it sections.
40
46
  const home = (await c.pages.find(EN, "/"))!;
41
47
  if (Object.values((await c.pages.getTree(home.id, EN))?.zones ?? {}).some((nodes) => nodes.length > 0)) {
42
48
  console.log(`Already seeded — the home page has content in "${EN}".`);
@@ -1,59 +1,46 @@
1
- // Server-only glue: the client, the stores, the site layer and the
2
- // renderer, constructed lazily so `next build` doesn't need env vars at
3
- // import time. The env shape is the package's (SMOODLY_*): the live site
4
- // needs SMOODLY_URL and SMOODLY_SECRET_KEY, the admin also
5
- // SMOODLY_PUBLISHABLE_KEY (the `smoodlyEnv("admin")` calls below); whether the
6
- // secret is a service-role key or a cloud write key is invisible here.
1
+ // Server-only glue: the reads over Smoodly's API and the renderer,
2
+ // constructed lazily so `next build` doesn't need env vars at import time.
3
+ // The env shape is the package's (SMOODLY_*): the live site and the admin need
4
+ // SMOODLY_API_URL, SMOODLY_SPACE_ID and SMOODLY_SECRET_KEY; the admin learns
5
+ // where editors sign in from Smoodly's API. The site opens no database.
7
6
  //
8
7
  // Lazy on purpose: a page component imports this file to read through
9
8
  // `q` (components/pages/Articles.tsx), and this file imports the
10
9
  // components through ./views. That is a cycle, so nothing here reads
11
10
  // views() or the env at module scope.
12
- import {
13
- createSmoodlyClient, createSmoodlySite, createSupabaseStores, presenceConfig, siteOptions, smoodlyEnv,
14
- type PresenceConfig, type SmoodlySite, type SupabaseAssetStore, type SupabaseEntryStore, type SupabaseMemberStore, type SupabasePageStore,
15
- } from "smoodly";
16
- // The admin's server pieces: the sign-in check and the content API. No browser code lives in the package's
17
- // entries; the admin itself is prebuilt and served by app/(smoodly)/admin/[[...segments]]/route.ts.
18
- import { createSmoodlyContent, supabaseAdminAuth, type AdminAuth, type AuthConfig, type SmoodlyContent } from "smoodly/admin";
11
+ import { apiEnv, createSmoodlyApi, presenceConfig, smoodlyEnv, type ContentReads, type PresenceConfig } from "smoodly";
12
+ // The admin's server pieces: the content API for scripts. No browser code lives in the package's entries;
13
+ // the admin itself is prebuilt and served by app/(smoodly)/admin/[[...segments]]/route.ts.
14
+ import { apiTransport, createSmoodlyContent, type SmoodlyContent } from "smoodly/admin";
19
15
  import { buildGate, createPageRenderer, createSmoodlyQueries } from "smoodly/next";
20
- import type { SupabaseClient } from "@supabase/supabase-js";
21
16
  import { collections, config, globals } from "./config";
22
17
  import { views } from "./views";
23
18
 
24
19
  type Renderer = ReturnType<typeof createPageRenderer>;
25
20
  type Queries = ReturnType<typeof createSmoodlyQueries<typeof collections, typeof globals>>;
26
- type Bound = {
27
- db: SupabaseClient; pages: SupabasePageStore; entries: SupabaseEntryStore; assets: SupabaseAssetStore; members: SupabaseMemberStore; auth: AdminAuth; site: SmoodlySite; renderer: Renderer; q: Queries;
28
- };
21
+ type Bound = { site: ContentReads; renderer: Renderer; q: Queries };
29
22
 
30
23
  let cached: Bound | null = null;
31
24
 
32
25
  function bound(): Bound {
33
26
  if (cached) return cached;
34
- const env = smoodlyEnv();
35
- const db = createSmoodlyClient(env);
36
- // The stores, read off the config: pages, entries, the media library and the admission table.
37
- const { pages, entries, assets, members, published } = createSupabaseStores(db, config);
38
- const auth = supabaseAdminAuth({ authUrl: env.authUrl, authKey: env.authKey, db });
39
- // The site layer: (locale, path) → page or entry, and the content API
40
- // `q` wraps below. Not exported — the app reads through `q`, which adds
41
- // draft awareness and the cache.
42
- const site = createSmoodlySite(siteOptions(config, () => ({ pages, entries, assets, published })));
27
+ // The site's reads go over Smoodly's API (SMOODLY_API_URL, SMOODLY_SPACE_ID, SMOODLY_SECRET_KEY):
28
+ // (locale, path) → page or entry, and the reads `q` wraps below. Not exported — the app reads
29
+ // through `q`, which adds draft awareness and the cache.
30
+ const site = createSmoodlyApi({ ...apiEnv(), config });
43
31
  // The components, checked against the config's schemas here, on first use.
44
32
  const renderer = createPageRenderer({ site, ...views(), href });
45
33
  // The read API (spec 2026-09-11): typed by the registry, draft-aware, cached under tags the admin expires.
46
34
  const q = createSmoodlyQueries({ site, collections, globals });
47
- cached = { db, pages, entries, assets, members, auth, site, renderer, q };
35
+ cached = { site, renderer, q };
48
36
  return cached;
49
37
  }
50
38
 
51
- /** The admin's wiring, for the admin route only. Scripts write through `content()`. */
39
+ /** The admin's wiring, for the admin route only: the page and its files, the revalidate route the
40
+ * service calls and the preview route the browser posts the grant to. Operations run in the service. */
52
41
  export function adminDeps() {
53
- smoodlyEnv("admin"); // the admin signs in with the publishable key: a missing one is named here, not on a live page
54
- const { pages, entries, assets, members, auth, site } = bound();
55
- // The projector: a publish writes the finished documents a live page reads.
56
- return { pages, entries, assets, members, auth, href, projector: site.projector };
42
+ const env = smoodlyEnv("admin"); // the admin talks to SMOODLY_API_URL, which also tells it where editors sign in
43
+ return { apiUrl: env.apiUrl, spaceId: env.spaceId, delivery: "live" as const };
57
44
  }
58
45
 
59
46
  /** Write content from code — seeds, migrations, route handlers (spec 2026-09-30 tester findings §1).
@@ -61,24 +48,15 @@ export function adminDeps() {
61
48
  * throws `SmoodlyContentError`. `content().pages.create(…)`, `.entries.create(…)`, `.globals.set(…)`,
62
49
  * `.media.upload(…)`; build tree nodes with `node(Section, fields)`. */
63
50
  export function content(): SmoodlyContent {
64
- const { pages, entries, assets, members, site } = bound();
65
- return createSmoodlyContent({ config, pages, entries, assets, members, href, projector: site.projector });
51
+ // Over Smoodly's API with the secret key: the service runs the operations.
52
+ return createSmoodlyContent({ config, href, transport: apiTransport(apiEnv()) });
66
53
  }
67
54
 
68
55
  export { node } from "smoodly";
69
56
 
70
- /** The server-side client (seed script, one-off admin tasks). */
71
- export const client = () => bound().db;
72
-
73
- /** What the browser needs to sign in — public values only. */
74
- export function publicAuth(): AuthConfig {
75
- const env = smoodlyEnv("admin");
76
- return { url: env.authUrl, key: env.authKey };
77
- }
78
-
79
- /** What the browser needs to join the editing-presence channel — public values only (spec 2026-09-26 concurrency §5).
80
- * The space is asked of the database once per process. */
81
- export function presence(): Promise<PresenceConfig> {
57
+ /** What the browser needs to open the editing-presence socket — Smoodly's API and the space, public values only
58
+ * (spec 2026-10-15 the space object §6). */
59
+ export function presence(): PresenceConfig {
82
60
  return presenceConfig(smoodlyEnv("admin"));
83
61
  }
84
62
 
@@ -105,7 +83,7 @@ export const q = (): Queries =>
105
83
  // route file without a dynamic segment reads nothing at build and the build needs no keys — a layout
106
84
  // reading a global through q() included (2026-10-02, tester round 6). At run time it does nothing: a
107
85
  // CMS route renders on its first visit and is cached until a publish expires it (plan 2026-10-09).
108
- /** The two route files call these. */
86
+ /** The route file calls these. */
109
87
  export const renderSmoodlyPath: Renderer["renderSmoodlyPath"] = async (...args) => { await buildGate(); return bound().renderer.renderSmoodlyPath(...args); };
110
88
  export const smoodlyMetadata: Renderer["smoodlyMetadata"] = async (...args) => { await buildGate(); return bound().renderer.smoodlyMetadata(...args); };
111
89
  export const renderSmoodlyPage: Renderer["renderSmoodlyPage"] = async (...args) => { await buildGate(); return bound().renderer.renderSmoodlyPage(...args); };
@@ -1,9 +1,9 @@
1
1
  # Working in this Smoodly site
2
2
 
3
- This is a Next.js app with Smoodly installed: content lives in Supabase,
4
- the admin is mounted at /admin from the `smoodly` package, and the site
5
- reads content through `smoodly/server.ts`. Read README.md first: it says
6
- where things are.
3
+ This is a Next.js app with Smoodly installed: content lives in Smoodly's service,
4
+ reached through the API, the admin is mounted at /admin from the `smoodly`
5
+ package, and the site reads content through `smoodly/server.ts`. Read
6
+ README.md first: it says where things are.
7
7
 
8
8
  ## Rules
9
9
 
@@ -20,12 +20,6 @@ where things are.
20
20
  `smoodly/server.ts` or `smoodly/richtext.tsx`. Zones, toolsets, global
21
21
  items and `node()` take schemas (`z.locked(heroSchema)`), never
22
22
  components.
23
- - **After a new collection or a `smoodly` upgrade, run `npx smoodly migrate`**
24
- and apply what it writes (`supabase db push` hosted, `supabase migration up`
25
- local). It copies the core schema files the package ships and writes a
26
- read view and order index per collection. Never edit a file it wrote.
27
- The admin reads the base tables, so it lists a collection either way;
28
- the view is for SQL and PostgREST readers, the index for `order`.
29
23
  - **Content from code goes through `content()`.** `scripts/seed.ts` is
30
24
  starter content written with `content()` from `smoodly/server.ts`:
31
25
  the admin's own operations, so validation and publish checks apply and
@@ -35,8 +29,8 @@ where things are.
35
29
  serves the admin's page, its files and its operations from the
36
30
  `smoodly` package; the folder name must match `admin.path` in the
37
31
  config. The admin is prebuilt: nothing of it is compiled into the
38
- site, and it takes no layout. Server-side pieces such as
39
- `supabaseAdminAuth` and the content API come from `smoodly/admin`.
32
+ site, and it takes no layout. The content API (`createSmoodlyContent`,
33
+ `apiTransport`) comes from `smoodly/admin`.
40
34
  - **One root layout.** The site's layout lives in its route group or
41
35
  locale segment. Do not add an `app/layout.tsx`: it would wrap that
42
36
  layout in a second `<html>`.
@@ -59,6 +53,6 @@ where things are.
59
53
  ## Commands
60
54
 
61
55
  npm run dev # http://localhost:3000, /admin is the editor
62
- npm run build # checks the schema files (smoodly migrate --check), then next build
56
+ npm run build # next build
63
57
  npm run seed # starter content, idempotent
64
58
  npm run typecheck # tsc --noEmit
@@ -1,8 +1,8 @@
1
1
  # Smoodly site
2
2
 
3
- Created by `create-smoodly-app`. Content lives in Supabase; the admin is
4
- mounted at `/admin` from the `smoodly` package. AGENTS.md holds the rules
5
- for an agent (or a person) building the site.
3
+ Created by `create-smoodly-app`. Content lives in Smoodly's service; the
4
+ admin is mounted at `/admin` from the `smoodly` package. AGENTS.md holds
5
+ the rules for an agent (or a person) building the site.
6
6
 
7
7
  npm run dev # http://localhost:3000 — /admin is the editor
8
8
 
@@ -17,9 +17,9 @@ for an agent (or a person) building the site.
17
17
  - `components/strings.ts`, `messages/` — the site's own UI strings and the request locale, one seam.
18
18
  - `public/images/` — the starter's photos; image fields hold URLs like `/images/hero_image.jpg`.
19
19
  - `scripts/seed.ts` — the starter content, written through the content API (`content()`). `npm run seed` is idempotent.
20
- - `smoodly/collections.ts` — structured content (people, articles, news). Add a collection, run `npx smoodly migrate`, apply the file it writes.
20
+ - `smoodly/collections.ts` — structured content (people, articles, news). Add a collection to the list; nothing else to apply.
21
21
  - `smoodly/` — the wiring: `config.ts` (the registry of schemas and the settings; it imports no component), `collections.ts`, `sections.ts` and `elements.ts` (the schemas the config registers), `views.ts` (the components that render them), `server.ts` (site layer, renderer, the content API `content()`).
22
- - `.env.local` — `SMOODLY_URL`, `SMOODLY_SECRET_KEY`, `SMOODLY_PUBLISHABLE_KEY`.
22
+ - `.env.local` — `SMOODLY_SPACE_ID`, `SMOODLY_SECRET_KEY`, `SMOODLY_PUBLISHABLE_KEY` (the space's values from smoodly.io) and `SITE_URL`.
23
23
 
24
24
  Translated URLs: a page's slug and title are edited per locale in the
25
25
  admin's page settings; a collection's base segment per locale is its
@@ -39,7 +39,7 @@ The Lorem Co. site the installer seeded is a tour of the features:
39
39
  - **Global content** — the header every page renders through `components/PageFrame.tsx`, the footer the layout renders, the contact details the footer reads, the quote pages place.
40
40
 
41
41
  Every page and entry exists in each locale with its own slug. Reseed an
42
- empty database any time with `npm run seed`.
42
+ empty space any time with `npm run seed`.
43
43
 
44
44
  ## Moving the admin
45
45
 
@@ -54,16 +54,12 @@ The two must agree; the admin says so when they do not.
54
54
 
55
55
  ## Editors
56
56
 
57
- Anyone who may open `/admin` is a Supabase Auth user with a row in the
58
- `editors` table. To add one: create the user in Supabase Studio
59
- (Authentication → Users), then in the SQL editor:
57
+ Editors sign in at `/admin` with their smoodly.io account. Colleagues are
58
+ invited from the project at smoodly.io; the admin's Members page lists them.
60
59
 
61
- insert into editors (user_id, role) values ('<the user id>', 'editor');
60
+ ## Deploying
62
61
 
63
- ## Hosted Supabase
64
-
65
- supabase init # if the CLI has not been set up here yet
66
- supabase link # pick the project
67
- supabase db push # applies supabase/migrations/
68
-
69
- Then put the project's URL and keys in `.env.local`.
62
+ `.env.local` holds the preview space's values, for development. Where you
63
+ deploy, set the production space's three values and `SITE_URL` in the
64
+ host's environment variables; each build pushes the
65
+ schema to its space. https://smoodly.io/docs/getting-started/deploying
@@ -1,8 +1,9 @@
1
- // The admin: its page, the package's files and the operations endpoint, all from this one route. The
2
- // admin is prebuilt in the `smoodly` package; nothing of it is compiled into the site. The folder name
3
- // must match `admin.path` in smoodly/config.ts.
1
+ // The admin: its page, the package's files, the revalidate route Smoodly's API calls after a publish and
2
+ // the preview route, all from this one route; the operations run in Smoodly's API. The admin is prebuilt
3
+ // in the `smoodly` package; nothing of it is compiled into the site. The folder name must match
4
+ // `admin.path` in smoodly/config.ts.
4
5
  import { createAdminRoute } from "smoodly/next";
5
6
  import { config } from "@/smoodly/config";
6
- import { adminDeps, presence, publicAuth } from "@/smoodly/server";
7
+ import { adminDeps, presence } from "@/smoodly/server";
7
8
 
8
- export const { GET, POST } = createAdminRoute(() => ({ config, ...adminDeps(), signIn: publicAuth(), presence }));
9
+ export const { GET, POST } = createAdminRoute(() => ({ config, ...adminDeps(), presence }));
@@ -4,6 +4,8 @@ import { f, smoodly } from "smoodly";
4
4
  // text and an optional wide photo. Page and Articles lock it — each page
5
5
  // shape locks its own hero, which is the point: a locked zone is per
6
6
  // shape. Without an image the copy stands alone (the Articles index).
7
+ // The photo has two named crops: wide on a desktop, tall on a phone; the
8
+ // view says which applies where.
7
9
  export const pageHeroSchema = smoodly.schema.section({
8
10
  name: "pageHero",
9
11
  title: "Page hero",
@@ -12,7 +14,7 @@ export const pageHeroSchema = smoodly.schema.section({
12
14
  kicker: f.text().optional(),
13
15
  heading: f.text(),
14
16
  text: f.text(),
15
- image: f.image({ aspect: "21:9" }).optional(),
17
+ image: f.image({ crops: { wide: "21:9", tall: "4:5" } }).optional(),
16
18
  },
17
19
  sample: {
18
20
  heading: "About us",