@sudajs/cli 0.18.8 → 0.18.10

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.
@@ -0,0 +1,206 @@
1
+ # Templates and Locales
2
+
3
+ Read this guide before changing CMS templates, post resources, starter pages,
4
+ authored page data, editor locales, preview content locales, or language links.
5
+ The root `AGENTS.md` rules still apply.
6
+
7
+ ## Authored page data
8
+
9
+ - Wrap hand-authored data with `defineSudaPageData(pageConfig, data)` when
10
+ practical.
11
+ - Every page uses `root: { props: {} }` and a `content` array.
12
+ - Every top-level `content[].type` must exactly match a public key in
13
+ `pageConfig.components`.
14
+ - Every top-level item needs a stable `props.id`.
15
+ - Do not put `id` in component `defaultProps`, local block `defaultProps`,
16
+ `defaultBlocks`, or block-slot nested items.
17
+ - Nested block-slot items use the short kind from
18
+ `blockSlots.<slotName>.blocks`, such as `"button"`, `"copy"`, or `"cards"`.
19
+ Never author `__suda_local_block__/...`.
20
+ - Use public authored props exactly as typed. Do not guess alternate field
21
+ names or add compatibility fallbacks.
22
+
23
+ ```ts
24
+ defineSudaPageData(pageConfig, {
25
+ root: { props: {} },
26
+ content: [
27
+ {
28
+ type: "Hero",
29
+ props: {
30
+ id: "Hero-1",
31
+ title: "Welcome",
32
+ actions: [{ type: "button", props: { label: "Contact us", href: "/contact" } }],
33
+ },
34
+ },
35
+ ],
36
+ });
37
+ ```
38
+
39
+ ## CMS contract
40
+
41
+ Suda CMS is a fixed post system. Themes provide exactly four entries through
42
+ `cmsTemplates`:
43
+
44
+ - `posts`: list at `/posts`.
45
+ - `post`: detail at `/posts/:slug`.
46
+ - `tags`: tag/category list at `/tags`.
47
+ - `tag`: posts for one tag at `/tags/:slug`.
48
+
49
+ Each entry is `{ title: string; data: PageData }`, not raw `PageData`:
50
+
51
+ ```ts
52
+ export const cmsTemplates: ThemeCmsTemplates = {
53
+ posts: { title: "Posts", data: postIndexTemplate },
54
+ post: { title: "Post", data: postDetailTemplate },
55
+ tags: { title: "Tags", data: tagIndexTemplate },
56
+ tag: { title: "Tag", data: tagDetailTemplate },
57
+ };
58
+ ```
59
+
60
+ Do not bypass the contract with intersection casts. Do not create variants such
61
+ as `post.default`, `post.modern`, or `posts.magazine`, arbitrary collection
62
+ types, or manual data-source binding contracts.
63
+
64
+ CMS-only main sections:
65
+
66
+ - `MainPosts`: `placement: { cmsTemplates: ["posts", "tag"] }` when shared;
67
+ render `cms.posts`, `cms.pagination`, and optional `cms.currentTag`.
68
+ - `MainPost`: only `post`; render when `cms.type === "post"`.
69
+ - `MainTags`: only `tags`; render when `cms.type === "tags"`.
70
+ - A separate `MainTagPosts` is valid when clearer than sharing `MainPosts`.
71
+
72
+ Read CMS data through `getCmsContent(puck?.metadata)`. CMS main sections must
73
+ not declare a `posts` field.
74
+
75
+ For pagination, use the exact platform fields:
76
+
77
+ - `cms.pagination.page`
78
+ - `cms.pagination.totalPages`
79
+ - `cms.pagination.hasPreviousPage`
80
+ - `cms.pagination.hasNextPage`
81
+ - `cms.pagination.previousUrl`
82
+ - `cms.pagination.nextUrl`
83
+
84
+ Use `cmsPaginationUrl(cms.pagination, targetPage)` for numbered links so current
85
+ filter/search parameters survive. Do not hand-build query strings.
86
+
87
+ ## Ordinary page post sections
88
+
89
+ Provide at least one reusable ordinary-page post section such as
90
+ `FeaturedPosts`, `LatestPosts`, or `TopicPosts`. It has exactly one top-level
91
+ `posts` field and a default query on the same prop:
92
+
93
+ ```ts
94
+ fields: {
95
+ postList: { type: "posts", label: t("sections.featuredPosts.fields.posts") },
96
+ },
97
+ defaultProps: {
98
+ postList: { strategy: "featured", limit: 3 },
99
+ },
100
+ ```
101
+
102
+ Supported strategies are `latest`, `featured`, `by_tag`, and `manual`. Manual
103
+ queries contain only `items` and must not contain `limit`.
104
+
105
+ Resolve data with the exact component id and field key:
106
+
107
+ ```ts
108
+ const posts = getPostResource(puck?.metadata, {
109
+ componentId: props.id,
110
+ fieldKey: "postList",
111
+ });
112
+ ```
113
+
114
+ The platform keys resources from both values. Do not import Prisma, call
115
+ platform APIs, or query a database from theme code. Do not add
116
+ `resourceQuery`, `resource_query`, assume the field is named `query`, nest a
117
+ posts field, or declare more than one posts field on a section.
118
+
119
+ For returned posts, prefer `post.url`. If missing, derive only from `post.slug`
120
+ as `/posts/${encodeURIComponent(post.slug)}`; if neither exists, use `#`. Do not
121
+ probe guessed fields such as `href`, `path`, `permalink`, `postUrl`, or
122
+ `canonicalUrl`.
123
+
124
+ ## Starter pages
125
+
126
+ - Define realistic starter pages in `src/templates.ts`; do not ship placeholder
127
+ pages or a mechanical list of every component.
128
+ - Include one home/index page with `isHome: true`.
129
+ - A strong theme usually includes several purposeful pages such as `index`,
130
+ `about-us`, `contact-us`, `services`, and `team`, but only the home page is a
131
+ hard requirement.
132
+ - Reuse conversion sections when they support the visitor journey, but vary
133
+ page purpose, copy, proof, media, and ordering so pages are not interchangeable.
134
+ - Use only registered public component keys, stable top-level ids, and short
135
+ block-slot kinds.
136
+ - Use `themeAsset("assets/...")` for bundled media.
137
+ - Starter slugs preview at root-like paths: `index` is `/index`, `contact-us`
138
+ is `/contact-us`. Never use `/pages/...`.
139
+
140
+ ## Editor labels
141
+
142
+ The host localizes editor configuration through `src/i18n.ts`.
143
+
144
+ - Use `t("stable.dot.path")` for component, category, field, nested field,
145
+ local-block, and option labels.
146
+ - Add every key to `src/locales/en.json`, the required reference locale.
147
+ - Locale JSON must be nested objects with string leaves. Arrays, numbers,
148
+ booleans, and null are invalid.
149
+ - Every additional `src/locales/<locale>.json` must have exactly the same leaf
150
+ keys as `en.json`; missing or extra keys fail validation and publishing.
151
+ - The CLI copies locale files into `dist/locales/`. Never edit dist copies.
152
+ - Do not use editor `t(...)` in public content, starter copy, default prop
153
+ values, or public-site text.
154
+
155
+ Installed legacy themes may show translation keys when locale files are absent;
156
+ new themes must still pass locale validation.
157
+
158
+ ## Starter preview content locales
159
+
160
+ Use the separate `src/preview-i18n.ts` helper only in starter page templates for
161
+ visitor-visible titles, headings, descriptions, list copy, and button labels.
162
+
163
+ - Keep URLs, slugs, ids, component types, and asset paths as direct strings.
164
+ - Do not use preview translations in component labels, fields, `defaultProps`,
165
+ layout configuration, or CMS templates.
166
+ - When `manifest.previewLocales` exists, it must include `en`, and
167
+ `src/locales/en.content.json` is required.
168
+ - Every declared preview locale needs a matching
169
+ `src/locales/<locale>.content.json` with exactly the same string leaf keys as
170
+ `en.content.json`.
171
+ - Undeclared, missing, extra, or partial content locale files fail validation
172
+ and publishing.
173
+
174
+ Files named `<locale>.json` are editor messages. Files named
175
+ `<locale>.content.json` are starter preview content. Keep the two namespaces
176
+ separate.
177
+
178
+ ## Public language links
179
+
180
+ Read language targets with `getSiteLocale(puck?.metadata)` from
181
+ `@sudajs/theme-engine/runtime`. Render the host-provided `label` and `href`.
182
+ Do not derive language names or construct locale URLs in theme code.
183
+
184
+ ```tsx
185
+ const siteLocale = getSiteLocale(puck?.metadata);
186
+
187
+ return siteLocale?.locales.map((item) => (
188
+ <a key={item.locale} href={item.href} lang={item.locale}>
189
+ {item.label}
190
+ </a>
191
+ ));
192
+ ```
193
+
194
+ ## Verification
195
+
196
+ After changing templates or locales, run:
197
+
198
+ ```bash
199
+ pnpm typecheck
200
+ pnpm build
201
+ pnpm validate
202
+ suda theme check
203
+ ```
204
+
205
+ Open every starter and CMS preview route and verify content, links, empty states,
206
+ pagination, and locale variants.
@@ -4,6 +4,7 @@ import {
4
4
  createThemeDesignDefault,
5
5
  designSystemField,
6
6
  getIcpRecord,
7
+ getPublicSecurityRecord,
7
8
  getPageSlot,
8
9
  getWhiteLabel,
9
10
  resolveThemeDesignTokens,
@@ -79,6 +80,7 @@ export const Footer: SudaComponentConfig<FooterProps> = {
79
80
  render: ({ text, puck }) => {
80
81
  const whiteLabel = getWhiteLabel(puck?.metadata);
81
82
  const icp = getIcpRecord(puck?.metadata);
83
+ const publicSecurityRecord = getPublicSecurityRecord(puck?.metadata);
82
84
  return (
83
85
  <footer className="__SUDA_THEME_KEY__-footer">
84
86
  <span>{text}</span>
@@ -93,6 +95,11 @@ export const Footer: SudaComponentConfig<FooterProps> = {
93
95
  {icp.text}
94
96
  </a>
95
97
  ) : null}
98
+ {publicSecurityRecord ? (
99
+ <a href={publicSecurityRecord.href} target="_blank" rel="noreferrer">
100
+ {publicSecurityRecord.text}
101
+ </a>
102
+ ) : null}
96
103
  </footer>
97
104
  );
98
105
  },