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.
- package/README.md +22 -30
- package/dist/index.js +87 -329
- package/package.json +4 -5
- package/templates/astro-blank/AGENTS.md +2 -8
- package/templates/astro-blank/README.md +13 -23
- package/templates/astro-blank/package.json +2 -3
- package/templates/astro-blank/scripts/seed.ts +9 -3
- package/templates/astro-blank/src/smoodly/collections.ts +2 -2
- package/templates/astro-blank-intl/AGENTS.md +2 -8
- package/templates/astro-blank-intl/README.md +13 -23
- package/templates/astro-blank-intl/package.json +2 -3
- package/templates/astro-blank-intl/scripts/seed.ts +9 -3
- package/templates/astro-blank-intl/src/smoodly/collections.ts +2 -2
- package/templates/astro-lorem/AGENTS.md +2 -8
- package/templates/astro-lorem/README.md +12 -16
- package/templates/astro-lorem/package.json +2 -3
- package/templates/astro-lorem/scripts/seed.ts +9 -3
- package/templates/astro-lorem/src/components/sections/PageHero.schema.ts +3 -1
- package/templates/astro-lorem/src/sections/pageHero.astro +1 -1
- package/templates/astro-lorem-intl/AGENTS.md +2 -8
- package/templates/astro-lorem-intl/README.md +12 -16
- package/templates/astro-lorem-intl/package.json +2 -3
- package/templates/astro-lorem-intl/scripts/seed.ts +9 -3
- package/templates/astro-lorem-intl/src/components/sections/PageHero.schema.ts +3 -1
- package/templates/astro-lorem-intl/src/sections/pageHero.astro +1 -1
- package/templates/blank/AGENTS.md +7 -13
- package/templates/blank/README.md +12 -22
- package/templates/blank/app/(smoodly)/admin/[[...segments]]/route.ts +6 -5
- package/templates/blank/next.config.ts +1 -4
- package/templates/blank/package.json +4 -4
- package/templates/blank/scripts/seed.ts +9 -3
- package/templates/blank/smoodly/collections.ts +2 -2
- package/templates/blank/smoodly/server.ts +25 -47
- package/templates/blank-intl/AGENTS.md +7 -13
- package/templates/blank-intl/README.md +12 -22
- package/templates/blank-intl/app/(smoodly)/admin/[[...segments]]/route.ts +6 -5
- package/templates/blank-intl/next.config.ts +1 -4
- package/templates/blank-intl/package.json +4 -4
- package/templates/blank-intl/scripts/seed.ts +9 -3
- package/templates/blank-intl/smoodly/collections.ts +2 -2
- package/templates/blank-intl/smoodly/server.ts +25 -47
- package/templates/blank-intl.tsconfig.json +0 -3
- package/templates/blank.tsconfig.json +0 -3
- package/templates/lorem/AGENTS.md +7 -13
- package/templates/lorem/README.md +13 -17
- package/templates/lorem/app/(smoodly)/admin/[[...segments]]/route.ts +6 -5
- package/templates/lorem/components/sections/PageHero.schema.ts +3 -1
- package/templates/lorem/components/sections/PageHero.tsx +1 -1
- package/templates/lorem/next.config.ts +1 -4
- package/templates/lorem/package.json +4 -4
- package/templates/lorem/scripts/seed.ts +9 -3
- package/templates/lorem/smoodly/server.ts +25 -47
- package/templates/lorem-intl/AGENTS.md +7 -13
- package/templates/lorem-intl/README.md +13 -17
- package/templates/lorem-intl/app/(smoodly)/admin/[[...segments]]/route.ts +6 -5
- package/templates/lorem-intl/components/sections/PageHero.schema.ts +3 -1
- package/templates/lorem-intl/components/sections/PageHero.tsx +1 -1
- package/templates/lorem-intl/next.config.ts +1 -4
- package/templates/lorem-intl/package.json +4 -4
- package/templates/lorem-intl/scripts/seed.ts +9 -3
- package/templates/lorem-intl/smoodly/server.ts +25 -47
- package/templates/lorem-intl.tsconfig.json +0 -3
- package/templates/lorem.tsconfig.json +0 -3
- package/templates/astro-blank/supabase/migrations/20260926120000_smoodly_v1.sql +0 -535
- package/templates/astro-blank/supabase/migrations/20260926130000_smoodly_v2.sql +0 -26
- package/templates/astro-blank/supabase/migrations/20260926140000_smoodly_v3.sql +0 -38
- package/templates/astro-blank/supabase/migrations/20260926150000_smoodly_v4.sql +0 -41
- package/templates/astro-blank/supabase/migrations/20260929120000_smoodly_v5.sql +0 -56
- package/templates/astro-blank/supabase/migrations/20260929135523_smoodly_v6.sql +0 -200
- package/templates/astro-blank/supabase/migrations/20260930040956_smoodly_v7.sql +0 -84
- package/templates/astro-blank/supabase/migrations/20261007120000_smoodly_v8.sql +0 -154
- package/templates/astro-blank/supabase/migrations/20261007200000_smoodly_v9.sql +0 -30
- package/templates/astro-blank/supabase/migrations/20261008120000_smoodly_v10.sql +0 -47
- package/templates/astro-blank/supabase/migrations/20261009120000_smoodly_v11.sql +0 -49
- package/templates/astro-blank/supabase/migrations/20261009120100_smoodly_views.sql +0 -20
- package/templates/astro-blank-intl/supabase/migrations/20260926120000_smoodly_v1.sql +0 -535
- package/templates/astro-blank-intl/supabase/migrations/20260926130000_smoodly_v2.sql +0 -26
- package/templates/astro-blank-intl/supabase/migrations/20260926140000_smoodly_v3.sql +0 -38
- package/templates/astro-blank-intl/supabase/migrations/20260926150000_smoodly_v4.sql +0 -41
- package/templates/astro-blank-intl/supabase/migrations/20260929120000_smoodly_v5.sql +0 -56
- package/templates/astro-blank-intl/supabase/migrations/20260929135523_smoodly_v6.sql +0 -200
- package/templates/astro-blank-intl/supabase/migrations/20260930040956_smoodly_v7.sql +0 -84
- package/templates/astro-blank-intl/supabase/migrations/20261007120000_smoodly_v8.sql +0 -154
- package/templates/astro-blank-intl/supabase/migrations/20261007200000_smoodly_v9.sql +0 -30
- package/templates/astro-blank-intl/supabase/migrations/20261008120000_smoodly_v10.sql +0 -47
- package/templates/astro-blank-intl/supabase/migrations/20261009120000_smoodly_v11.sql +0 -49
- package/templates/astro-blank-intl/supabase/migrations/20261009120100_smoodly_views.sql +0 -20
- package/templates/astro-lorem/supabase/migrations/20260926120000_smoodly_v1.sql +0 -535
- package/templates/astro-lorem/supabase/migrations/20260926130000_smoodly_v2.sql +0 -26
- package/templates/astro-lorem/supabase/migrations/20260926140000_smoodly_v3.sql +0 -38
- package/templates/astro-lorem/supabase/migrations/20260926150000_smoodly_v4.sql +0 -41
- package/templates/astro-lorem/supabase/migrations/20260929120000_smoodly_v5.sql +0 -56
- package/templates/astro-lorem/supabase/migrations/20260929135523_smoodly_v6.sql +0 -200
- package/templates/astro-lorem/supabase/migrations/20260930040956_smoodly_v7.sql +0 -84
- package/templates/astro-lorem/supabase/migrations/20261007120000_smoodly_v8.sql +0 -154
- package/templates/astro-lorem/supabase/migrations/20261007200000_smoodly_v9.sql +0 -30
- package/templates/astro-lorem/supabase/migrations/20261008120000_smoodly_v10.sql +0 -47
- package/templates/astro-lorem/supabase/migrations/20261009120000_smoodly_v11.sql +0 -49
- package/templates/astro-lorem/supabase/migrations/20261009120100_smoodly_views.sql +0 -58
- package/templates/astro-lorem-intl/supabase/migrations/20260926120000_smoodly_v1.sql +0 -535
- package/templates/astro-lorem-intl/supabase/migrations/20260926130000_smoodly_v2.sql +0 -26
- package/templates/astro-lorem-intl/supabase/migrations/20260926140000_smoodly_v3.sql +0 -38
- package/templates/astro-lorem-intl/supabase/migrations/20260926150000_smoodly_v4.sql +0 -41
- package/templates/astro-lorem-intl/supabase/migrations/20260929120000_smoodly_v5.sql +0 -56
- package/templates/astro-lorem-intl/supabase/migrations/20260929135523_smoodly_v6.sql +0 -200
- package/templates/astro-lorem-intl/supabase/migrations/20260930040956_smoodly_v7.sql +0 -84
- package/templates/astro-lorem-intl/supabase/migrations/20261007120000_smoodly_v8.sql +0 -154
- package/templates/astro-lorem-intl/supabase/migrations/20261007200000_smoodly_v9.sql +0 -30
- package/templates/astro-lorem-intl/supabase/migrations/20261008120000_smoodly_v10.sql +0 -47
- package/templates/astro-lorem-intl/supabase/migrations/20261009120000_smoodly_v11.sql +0 -49
- package/templates/astro-lorem-intl/supabase/migrations/20261009120100_smoodly_views.sql +0 -58
- package/templates/blank/supabase/migrations/20260926120000_smoodly_v1.sql +0 -535
- package/templates/blank/supabase/migrations/20260926130000_smoodly_v2.sql +0 -26
- package/templates/blank/supabase/migrations/20260926140000_smoodly_v3.sql +0 -38
- package/templates/blank/supabase/migrations/20260926150000_smoodly_v4.sql +0 -41
- package/templates/blank/supabase/migrations/20260929120000_smoodly_v5.sql +0 -56
- package/templates/blank/supabase/migrations/20260929135523_smoodly_v6.sql +0 -200
- package/templates/blank/supabase/migrations/20260930040956_smoodly_v7.sql +0 -84
- package/templates/blank/supabase/migrations/20261007120000_smoodly_v8.sql +0 -154
- package/templates/blank/supabase/migrations/20261007200000_smoodly_v9.sql +0 -30
- package/templates/blank/supabase/migrations/20261008120000_smoodly_v10.sql +0 -47
- package/templates/blank/supabase/migrations/20261009120000_smoodly_v11.sql +0 -49
- package/templates/blank/supabase/migrations/20261009120100_smoodly_views.sql +0 -20
- package/templates/blank-intl/supabase/migrations/20260926120000_smoodly_v1.sql +0 -535
- package/templates/blank-intl/supabase/migrations/20260926130000_smoodly_v2.sql +0 -26
- package/templates/blank-intl/supabase/migrations/20260926140000_smoodly_v3.sql +0 -38
- package/templates/blank-intl/supabase/migrations/20260926150000_smoodly_v4.sql +0 -41
- package/templates/blank-intl/supabase/migrations/20260929120000_smoodly_v5.sql +0 -56
- package/templates/blank-intl/supabase/migrations/20260929135523_smoodly_v6.sql +0 -200
- package/templates/blank-intl/supabase/migrations/20260930040956_smoodly_v7.sql +0 -84
- package/templates/blank-intl/supabase/migrations/20261007120000_smoodly_v8.sql +0 -154
- package/templates/blank-intl/supabase/migrations/20261007200000_smoodly_v9.sql +0 -30
- package/templates/blank-intl/supabase/migrations/20261008120000_smoodly_v10.sql +0 -47
- package/templates/blank-intl/supabase/migrations/20261009120000_smoodly_v11.sql +0 -49
- package/templates/blank-intl/supabase/migrations/20261009120100_smoodly_views.sql +0 -20
- package/templates/lorem/supabase/migrations/20260926120000_smoodly_v1.sql +0 -535
- package/templates/lorem/supabase/migrations/20260926130000_smoodly_v2.sql +0 -26
- package/templates/lorem/supabase/migrations/20260926140000_smoodly_v3.sql +0 -38
- package/templates/lorem/supabase/migrations/20260926150000_smoodly_v4.sql +0 -41
- package/templates/lorem/supabase/migrations/20260929120000_smoodly_v5.sql +0 -56
- package/templates/lorem/supabase/migrations/20260929135523_smoodly_v6.sql +0 -200
- package/templates/lorem/supabase/migrations/20260930040956_smoodly_v7.sql +0 -84
- package/templates/lorem/supabase/migrations/20261007120000_smoodly_v8.sql +0 -154
- package/templates/lorem/supabase/migrations/20261007200000_smoodly_v9.sql +0 -30
- package/templates/lorem/supabase/migrations/20261008120000_smoodly_v10.sql +0 -47
- package/templates/lorem/supabase/migrations/20261009120000_smoodly_v11.sql +0 -49
- package/templates/lorem/supabase/migrations/20261009120100_smoodly_views.sql +0 -58
- package/templates/lorem-intl/supabase/migrations/20260926120000_smoodly_v1.sql +0 -535
- package/templates/lorem-intl/supabase/migrations/20260926130000_smoodly_v2.sql +0 -26
- package/templates/lorem-intl/supabase/migrations/20260926140000_smoodly_v3.sql +0 -38
- package/templates/lorem-intl/supabase/migrations/20260926150000_smoodly_v4.sql +0 -41
- package/templates/lorem-intl/supabase/migrations/20260929120000_smoodly_v5.sql +0 -56
- package/templates/lorem-intl/supabase/migrations/20260929135523_smoodly_v6.sql +0 -200
- package/templates/lorem-intl/supabase/migrations/20260930040956_smoodly_v7.sql +0 -84
- package/templates/lorem-intl/supabase/migrations/20261007120000_smoodly_v8.sql +0 -154
- package/templates/lorem-intl/supabase/migrations/20261007200000_smoodly_v9.sql +0 -30
- package/templates/lorem-intl/supabase/migrations/20261008120000_smoodly_v10.sql +0 -47
- package/templates/lorem-intl/supabase/migrations/20261009120000_smoodly_v11.sql +0 -49
- package/templates/lorem-intl/supabase/migrations/20261009120100_smoodly_views.sql +0 -58
|
@@ -1,60 +1,47 @@
|
|
|
1
|
-
// Server-only glue: the
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
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
|
-
|
|
14
|
-
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
//
|
|
38
|
-
const
|
|
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 = {
|
|
36
|
+
cached = { site, renderer, q };
|
|
49
37
|
return cached;
|
|
50
38
|
}
|
|
51
39
|
|
|
52
|
-
/** The admin's wiring, for the admin route only
|
|
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
|
|
55
|
-
|
|
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
|
-
|
|
66
|
-
return createSmoodlyContent({ config,
|
|
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
|
-
/**
|
|
72
|
-
|
|
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
|
|
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); };
|
|
@@ -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
|
|
4
|
-
the admin is mounted at /admin from the `smoodly`
|
|
5
|
-
reads content through `smoodly/server.ts`. Read
|
|
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.
|
|
39
|
-
`
|
|
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 #
|
|
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
|
|
4
|
-
mounted at `/admin` from the `smoodly` package. AGENTS.md holds
|
|
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
|
|
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` — `
|
|
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
|
|
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
|
-
|
|
48
|
-
|
|
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
|
-
|
|
50
|
+
## Deploying
|
|
52
51
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
|
2
|
-
//
|
|
3
|
-
//
|
|
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
|
|
7
|
+
import { adminDeps, presence } from "@/smoodly/server";
|
|
7
8
|
|
|
8
|
-
export const { GET, POST } = createAdminRoute(() => ({ config, ...adminDeps(),
|
|
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({
|
|
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
|
-
|
|
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": "
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
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
|
-
|
|
14
|
-
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
//
|
|
37
|
-
const
|
|
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 = {
|
|
35
|
+
cached = { site, renderer, q };
|
|
48
36
|
return cached;
|
|
49
37
|
}
|
|
50
38
|
|
|
51
|
-
/** The admin's wiring, for the admin route only
|
|
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
|
|
54
|
-
|
|
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
|
-
|
|
65
|
-
return createSmoodlyContent({ config,
|
|
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
|
-
/**
|
|
71
|
-
|
|
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
|
|
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
|
|
4
|
-
the admin is mounted at /admin from the `smoodly`
|
|
5
|
-
reads content through `smoodly/server.ts`. Read
|
|
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.
|
|
39
|
-
`
|
|
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 #
|
|
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
|
|
4
|
-
mounted at `/admin` from the `smoodly` package. AGENTS.md holds
|
|
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
|
|
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` — `
|
|
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
|
|
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
|
-
|
|
58
|
-
|
|
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
|
-
|
|
60
|
+
## Deploying
|
|
62
61
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
|
2
|
-
//
|
|
3
|
-
//
|
|
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
|
|
7
|
+
import { adminDeps, presence } from "@/smoodly/server";
|
|
7
8
|
|
|
8
|
-
export const { GET, POST } = createAdminRoute(() => ({ config, ...adminDeps(),
|
|
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({
|
|
17
|
+
image: f.image({ crops: { wide: "21:9", tall: "4:5" } }).optional(),
|
|
16
18
|
},
|
|
17
19
|
sample: {
|
|
18
20
|
heading: "About us",
|