@sudajs/cli 0.13.0 → 0.13.2
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sudajs/cli",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"suda": "./bin/suda.js"
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"react": "^19.2.7",
|
|
35
35
|
"react-dom": "^19.2.7",
|
|
36
36
|
"zod": "^3.24.1",
|
|
37
|
-
"@sudajs/theme-engine": "5.1.
|
|
37
|
+
"@sudajs/theme-engine": "5.1.2"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@tailwindcss/postcss": "^4.3.0",
|
|
@@ -71,6 +71,28 @@ one pass:
|
|
|
71
71
|
8. Run `pnpm typecheck`, `pnpm lint`, `pnpm build`, and `pnpm validate` before
|
|
72
72
|
handoff.
|
|
73
73
|
|
|
74
|
+
Rendering fallback rule:
|
|
75
|
+
|
|
76
|
+
- Defaults belong in `defaultProps`, starter page data, or CMS template data.
|
|
77
|
+
During render, read values directly from props. Do not add fallback display
|
|
78
|
+
values with `||`, `??`, or ternaries unless the fallback is truly required for
|
|
79
|
+
runtime safety.
|
|
80
|
+
- This is wrong because render invents content that is not in props:
|
|
81
|
+
|
|
82
|
+
```tsx
|
|
83
|
+
<div>{props.title || "title"}</div>
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
- This is correct because render only displays the authored prop value:
|
|
87
|
+
|
|
88
|
+
```tsx
|
|
89
|
+
<div>{props.title}</div>
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
- Conditional rendering is fine when absence intentionally removes optional UI,
|
|
93
|
+
such as hiding an optional image, button, or eyebrow. Do not use conditional
|
|
94
|
+
rendering to substitute placeholder copy, labels, links, or menu items.
|
|
95
|
+
|
|
74
96
|
Configure ordinary fields like this:
|
|
75
97
|
|
|
76
98
|
```ts
|
|
@@ -143,6 +165,145 @@ Field source of truth:
|
|
|
143
165
|
`range`, `spacing`, `media`, `image`, `video`, and `posts`. These are valid
|
|
144
166
|
in `fields` and are normalized by `@sudajs/theme-engine`.
|
|
145
167
|
|
|
168
|
+
## Navigation and footer menus
|
|
169
|
+
|
|
170
|
+
Navigation and footer menus must be typed arrays. Do not use the legacy menu
|
|
171
|
+
field type, multiline text areas, or newline-delimited strings to model menus.
|
|
172
|
+
Every link destination must use a `url` field type, never a plain `text` field.
|
|
173
|
+
The `fields`, TypeScript props, and `defaultProps` data structures must match
|
|
174
|
+
exactly.
|
|
175
|
+
|
|
176
|
+
Navigation supports at most two levels:
|
|
177
|
+
|
|
178
|
+
- `navItems[]`
|
|
179
|
+
- `navItems[].submenu[]`
|
|
180
|
+
|
|
181
|
+
Submenu items may contain only text and link fields. Do not add another nested
|
|
182
|
+
submenu field inside `navItems[].submenu[]`; the field shape must make third
|
|
183
|
+
levels impossible. An empty `submenu` array means the item is an ordinary
|
|
184
|
+
top-level link.
|
|
185
|
+
|
|
186
|
+
The simplest Navigation field shape is:
|
|
187
|
+
|
|
188
|
+
```ts
|
|
189
|
+
type NavSubItem = {
|
|
190
|
+
label: string;
|
|
191
|
+
href: string;
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
type NavItem = {
|
|
195
|
+
label: string;
|
|
196
|
+
href: string;
|
|
197
|
+
submenu: NavSubItem[];
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
const navItemsField = {
|
|
201
|
+
type: "array",
|
|
202
|
+
label: t("common.fields.navigationItems"),
|
|
203
|
+
getItemSummary: (item: NavItem) => item.label || "Navigation item",
|
|
204
|
+
defaultItemProps: {
|
|
205
|
+
label: "Home",
|
|
206
|
+
href: "/",
|
|
207
|
+
submenu: [],
|
|
208
|
+
},
|
|
209
|
+
arrayFields: {
|
|
210
|
+
label: {
|
|
211
|
+
type: "text",
|
|
212
|
+
label: t("common.fields.label"),
|
|
213
|
+
},
|
|
214
|
+
href: {
|
|
215
|
+
type: "url",
|
|
216
|
+
label: t("common.fields.link"),
|
|
217
|
+
},
|
|
218
|
+
submenu: {
|
|
219
|
+
type: "array",
|
|
220
|
+
label: t("common.fields.submenu"),
|
|
221
|
+
getItemSummary: (item: NavSubItem) => item.label || "Submenu item",
|
|
222
|
+
arrayFields: {
|
|
223
|
+
label: {
|
|
224
|
+
type: "text",
|
|
225
|
+
label: t("common.fields.label"),
|
|
226
|
+
},
|
|
227
|
+
href: {
|
|
228
|
+
type: "url",
|
|
229
|
+
label: t("common.fields.link"),
|
|
230
|
+
},
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
},
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
const defaultNavItems: NavItem[] = [
|
|
237
|
+
{
|
|
238
|
+
label: "Home",
|
|
239
|
+
href: "/",
|
|
240
|
+
submenu: [],
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
label: "Company",
|
|
244
|
+
href: "/company",
|
|
245
|
+
submenu: [
|
|
246
|
+
{
|
|
247
|
+
label: "About",
|
|
248
|
+
href: "/about",
|
|
249
|
+
},
|
|
250
|
+
],
|
|
251
|
+
},
|
|
252
|
+
];
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
Footer grouped menus use two array levels:
|
|
256
|
+
|
|
257
|
+
- `columns[]`
|
|
258
|
+
- `columns[].links[]`
|
|
259
|
+
|
|
260
|
+
Use this prop shape and mirror it exactly in the field config and defaults:
|
|
261
|
+
|
|
262
|
+
```ts
|
|
263
|
+
type FooterColumn = {
|
|
264
|
+
title: string;
|
|
265
|
+
links: {
|
|
266
|
+
text: string;
|
|
267
|
+
url: string;
|
|
268
|
+
}[];
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
const footerColumnsField = {
|
|
272
|
+
type: "array",
|
|
273
|
+
label: t("common.fields.columns"),
|
|
274
|
+
getItemSummary: (item: FooterColumn) => item.title || "Footer column",
|
|
275
|
+
defaultItemProps: {
|
|
276
|
+
title: "Company",
|
|
277
|
+
links: [{ text: "About", url: "/about" }],
|
|
278
|
+
},
|
|
279
|
+
arrayFields: {
|
|
280
|
+
title: {
|
|
281
|
+
type: "text",
|
|
282
|
+
label: t("common.fields.title"),
|
|
283
|
+
},
|
|
284
|
+
links: {
|
|
285
|
+
type: "array",
|
|
286
|
+
label: t("common.fields.links"),
|
|
287
|
+
getItemSummary: (item: FooterColumn["links"][number]) => item.text || "Footer link",
|
|
288
|
+
defaultItemProps: {
|
|
289
|
+
text: "About",
|
|
290
|
+
url: "/about",
|
|
291
|
+
},
|
|
292
|
+
arrayFields: {
|
|
293
|
+
text: {
|
|
294
|
+
type: "text",
|
|
295
|
+
label: t("common.fields.label"),
|
|
296
|
+
},
|
|
297
|
+
url: {
|
|
298
|
+
type: "url",
|
|
299
|
+
label: t("common.fields.link"),
|
|
300
|
+
},
|
|
301
|
+
},
|
|
302
|
+
},
|
|
303
|
+
},
|
|
304
|
+
};
|
|
305
|
+
```
|
|
306
|
+
|
|
146
307
|
Use these field patterns:
|
|
147
308
|
|
|
148
309
|
```ts
|
|
@@ -236,8 +397,6 @@ defaultProps: {
|
|
|
236
397
|
download: "",
|
|
237
398
|
icon: "sparkles",
|
|
238
399
|
accentColor: "#2563eb",
|
|
239
|
-
font: "system",
|
|
240
|
-
links: [{ label: "Home", url: "/" }],
|
|
241
400
|
spacing: "md",
|
|
242
401
|
postList: { strategy: "featured", limit: 3 },
|
|
243
402
|
badge: { label: "New", tone: "primary" },
|
|
@@ -288,6 +447,32 @@ or AI examples.
|
|
|
288
447
|
- React, React DOM, Puck, and `@sudajs/theme-engine` are host-provided peers. Do not bundle private copies into the theme runtime.
|
|
289
448
|
- Keep persisted props JSON-serializable. Do not store functions, React nodes, class instances, database ids for media, or environment-specific absolute filesystem paths.
|
|
290
449
|
|
|
450
|
+
## Puck editor CSS isolation
|
|
451
|
+
|
|
452
|
+
Puck renders the editable preview in a same-origin iframe. The outer
|
|
453
|
+
`#puck-canvas-root` and `#preview-frame` may be only one viewport tall; the
|
|
454
|
+
full page must scroll inside the iframe. Theme CSS must not collapse or disable
|
|
455
|
+
that iframe document.
|
|
456
|
+
|
|
457
|
+
- In editor mode, make the theme root content-height driven. A root marker such
|
|
458
|
+
as `[data-suda-editor="true"]` should use `height: auto`,
|
|
459
|
+
`min-height: 100vh`, and `overflow: visible` unless the theme has a stronger
|
|
460
|
+
reason not to.
|
|
461
|
+
- If the theme root is rendered inside Puck's `#frame-root`, ensure
|
|
462
|
+
`#frame-root` can grow with content in editor mode. Do not leave it stuck at
|
|
463
|
+
a collapsed height when it contains the theme root.
|
|
464
|
+
- Root `[data-puck-dropzone]` wrappers must be able to grow with page content.
|
|
465
|
+
Avoid forcing `height: 100%` on the root DropZone when that turns the page
|
|
466
|
+
into a single viewport-height box.
|
|
467
|
+
- Keep public-site resets from leaking into Puck behavior. Be very careful with
|
|
468
|
+
global `html`, `body`, `iframe`, `.hidden`, `img`, `button`, `input`, and
|
|
469
|
+
Tailwind preflight rules; they can hide editor wrappers, change intrinsic
|
|
470
|
+
media sizing, or break iframe scrolling.
|
|
471
|
+
- When importing third-party CSS, add editor-scoped overrides under the theme
|
|
472
|
+
root marker instead of changing public runtime behavior globally.
|
|
473
|
+
- Verify the editor canvas by scrolling through every starter page. Components
|
|
474
|
+
must be visible and reachable without selecting them from the outline first.
|
|
475
|
+
|
|
291
476
|
## CMS and starter templates
|
|
292
477
|
|
|
293
478
|
Suda CMS is a built-in fixed post system. Themes must provide exactly these
|
|
@@ -526,7 +711,8 @@ radius, shadows, or spacing.
|
|
|
526
711
|
|
|
527
712
|
- Tailwind v4 starts in `src/styles.css` with `@import "tailwindcss";` and `@source "./**/*.{ts,tsx}";`.
|
|
528
713
|
- Theme-local assets live in top-level `assets/`; the CLI copies them to `dist/assets/` during build.
|
|
529
|
-
-
|
|
714
|
+
- Import `themeAsset` from `./theme-asset.js` and use `themeAsset("assets/...")` for every theme-bundled asset in default props, starter pages, CMS templates, and AI examples.
|
|
715
|
+
- Do not hand-write `themes/<themeKey>/<version>/...` paths and do not import theme-local images from React code. Full external URLs are allowed, for example CDN URLs such as jsDelivr.
|
|
530
716
|
- Use `resolveAsset(puck?.metadata, value)` from `@sudajs/theme-engine/runtime` before rendering user-selected media fields.
|
|
531
717
|
- Scope theme CSS with the generated theme key classes. Avoid global resets that could affect the host editor or other themes.
|
|
532
718
|
- Preview screenshots are required at `assets/preview/desktop.png`, `assets/preview/tablet.png`, and `assets/preview/mobile.png`; run `suda theme capture` to generate them.
|
|
@@ -704,7 +890,7 @@ Starter page data should use the public host component type and a standard array
|
|
|
704
890
|
- Do not put local block `id` values in starter pages. Only top-level page components need `props.id`.
|
|
705
891
|
- Where practical, wrap authored data with `defineSudaPageData(pageConfig, data)` so TypeScript checks top-level component keys and block-slot nested `type` values.
|
|
706
892
|
- Match starter page content to the theme's intended audience and category. The home page should show the theme's best composition, not just every component in order.
|
|
707
|
-
- Use `themeAsset("assets/...")` for bundled starter media.
|
|
893
|
+
- Use `themeAsset("assets/...")` for bundled starter media; never hand-write generated theme asset paths.
|
|
708
894
|
|
|
709
895
|
## Commands
|
|
710
896
|
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createThemeAsset } from "@sudajs/theme-engine/runtime";
|
|
2
2
|
|
|
3
3
|
import { sourceManifest } from "./manifest.js";
|
|
4
4
|
|
|
5
|
-
export const themeAsset =
|
|
5
|
+
export const themeAsset = createThemeAsset({
|
|
6
|
+
...sourceManifest,
|
|
7
|
+
version: __SUDA_THEME_VERSION__,
|
|
8
|
+
});
|