@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.0",
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.0"
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
- - Use `themeAsset("assets/...")` for theme-bundled assets in default props and starter pages. Do not import images from React code.
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 { createThemeAssetResolver } from "@sudajs/theme-engine/runtime";
1
+ import { createThemeAsset } from "@sudajs/theme-engine/runtime";
2
2
 
3
3
  import { sourceManifest } from "./manifest.js";
4
4
 
5
- export const themeAsset = createThemeAssetResolver(sourceManifest.key, __SUDA_THEME_VERSION__);
5
+ export const themeAsset = createThemeAsset({
6
+ ...sourceManifest,
7
+ version: __SUDA_THEME_VERSION__,
8
+ });