@sudajs/cli 0.11.0 → 0.12.1

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.11.0",
3
+ "version": "0.12.1",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "suda": "./bin/suda.js"
@@ -25,7 +25,7 @@
25
25
  },
26
26
  "dependencies": {
27
27
  "@modelcontextprotocol/sdk": "^1.29.0",
28
- "@puckeditor/core": "^0.21.3",
28
+ "@puckeditor/core": "^0.22.0",
29
29
  "commander": "^12.1.0",
30
30
  "esbuild": "^0.25.12",
31
31
  "lucide-react": "^1.17.0",
@@ -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": "3.0.0"
37
+ "@sudajs/theme-engine": "4.0.0"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@tailwindcss/postcss": "^4.3.0",
@@ -49,8 +49,8 @@
49
49
  "typescript": "^5.7.2",
50
50
  "vite": "^7.3.3",
51
51
  "vitest": "^3.2.4",
52
- "@suda/eslint-config": "0.0.0",
53
52
  "@suda/build-config": "0.0.0",
53
+ "@suda/eslint-config": "0.0.0",
54
54
  "@suda/tsconfig": "0.0.0"
55
55
  },
56
56
  "scripts": {
@@ -320,10 +320,18 @@ defaultProps: {
320
320
  },
321
321
  ```
322
322
 
323
- Supported strategies are `latest`, `featured`, `by_tag`, and `manual`. In the
324
- section render function, call `getPostResource(puck?.metadata, id)` to get the
325
- posts. Never import Prisma, call platform APIs, or query the database from
326
- theme code.
323
+ Supported strategies are `latest`, `featured`, `by_tag`, and `manual`. Manual
324
+ queries store only `items` and must not include `limit`. Runtime post resources
325
+ are keyed as `${props.id}.${fieldKey}` with both parts URL-encoded by the
326
+ platform. In the section render function, call
327
+ `getPostResource(puck?.metadata, { componentId: props.id, fieldKey: "postList" })`
328
+ with the exact posts field key. Never import Prisma, call platform APIs, or
329
+ query the database from theme code.
330
+
331
+ Do not add `resourceQuery: { type: "posts" }` or
332
+ `resource_query: { type: "posts" }`. Do not assume the field must be named
333
+ `query`. A section may have only one top-level posts field; do not nest posts
334
+ fields inside `object`, `array`, native slots, or `blockSlots`.
327
335
 
328
336
  Starter pages must include one home/index page with `isHome: true`. Recommended
329
337
  starter pages include `index`, `about-us`, `contact-us`, `services`, and `team`.
@@ -406,6 +414,67 @@ export const Hero: SudaComponentConfig<HeroProps> = {
406
414
  - If a page section needs controlled nested content, use Suda `blockSlots`, not a hand-written Puck slot field. `blockSlots` lets the theme define exactly which local block kinds are allowed inside that section.
407
415
  - Do not use legacy DropZone or `zones` patterns.
408
416
 
417
+ ## Design system rules
418
+
419
+ Build every theme from one theme-level design system. Do not let each section,
420
+ CMS template, block slot, or starter page invent its own colors, type scale,
421
+ radius, shadows, or spacing.
422
+
423
+ - Define the editable theme system once in `src/manifest.ts` as
424
+ `sourceManifest.designSystem`. Include `version`, `defaultPresetId`, and
425
+ named `presets` with complete token sets.
426
+ - Use stable token names from the engine contract. At minimum, provide the
427
+ color tokens the theme renders (`background`, `foreground`, `primary`,
428
+ `primaryForeground`, `accent`, `accentForeground`, `muted`,
429
+ `mutedForeground`) and the radius tokens the CSS consumes (`card`, `button`,
430
+ `input`). Do not invent platform-facing token keys without checking the
431
+ generated types first.
432
+ - Expose the design system at the layout root only:
433
+ `designSystem: designSystemField(t("common.fields.designSystem"),
434
+ sourceManifest.designSystem)`, with
435
+ `createThemeDesignDefault(sourceManifest.designSystem)` in `ROOT_DEFAULTS`.
436
+ - In the root render, resolve the value once with
437
+ `resolveThemeDesignTokens(sourceManifest.designSystem, props.designSystem)`
438
+ and apply `createThemeDesignCssVariables(tokens)` to the theme root element.
439
+ Components should consume CSS variables; they should not resolve design
440
+ presets themselves.
441
+ - In `src/styles.css`, map Suda variables to theme-local or Tailwind variables
442
+ once, for example `--color-primary: var(--suda-color-primary, #...)` and
443
+ `--radius-card: var(--suda-radius-card, 24px)`. Use those variables across
444
+ sections, cards, CMS layouts, post lists, forms, and local blocks.
445
+ - Keep typography and spacing scales centralized in `src/styles.css`. Define
446
+ reusable classes or variables for containers, section padding, headings,
447
+ eyebrow text, body copy, cards, buttons, inputs, and media frames. Reuse those
448
+ classes instead of writing new one-off Tailwind values in every section.
449
+ - Section props may expose semantic choices such as `tone`, `variant`,
450
+ `columns`, `mediaPosition`, `showImage`, or `spacing`. Map those choices to
451
+ the existing design tokens and shared CSS classes.
452
+ - Do not add raw `color`, `font`, `spacing`, `radius`, or `shadow` fields to a
453
+ section just because the CSS has a value. Expose a design token field only
454
+ when the user should intentionally customize that value per component
455
+ instance. Otherwise, keep the value in the theme CSS.
456
+ - Use `color`, `font`, and `spacing` field types only for real editor-facing
457
+ design controls. Use `select`/`radio` for named variants that are already
458
+ part of the theme design system.
459
+ - Local blocks inside `blockSlots` inherit the host section's design system.
460
+ Local block fields should edit content or semantic variants, not define their
461
+ own palette, type scale, radius, shadows, or spacing.
462
+ - CMS main sections (`MainPosts`, `MainPost`, `MainTags`, `MainTagPosts`) and
463
+ ordinary post-resource sections must use the same containers, heading scale,
464
+ card style, media ratio, pagination style, and empty/loading states as the
465
+ rest of the theme.
466
+ - Starter pages must demonstrate the same design system across different page
467
+ types. Vary content, order, media, and semantic variants; do not hardcode
468
+ unrelated colors, spacing, rounded corners, or shadows in starter page data to
469
+ make pages look different.
470
+ - When adding a new section, first choose the existing container, heading,
471
+ button, card, form, and media patterns it should reuse. Add a new CSS utility
472
+ or token only when multiple sections will use it or the theme needs a new
473
+ named pattern.
474
+ - Keep preview screenshots, default props, starter pages, CMS templates, and
475
+ AI examples aligned with the same token presets. The default preset should
476
+ look publishable without manual editor tweaks.
477
+
409
478
  ## Styling and assets
410
479
 
411
480
  - Tailwind v4 starts in `src/styles.css` with `@import "tailwindcss";` and `@source "./**/*.{ts,tsx}";`.
@@ -19,7 +19,7 @@
19
19
  "devDependencies": {
20
20
  "@eslint/js": "^9.39.4",
21
21
  "@tailwindcss/postcss": "^4.3.0",
22
- "@puckeditor/core": "^0.21.3",
22
+ "@puckeditor/core": "^0.22.0",
23
23
  "@sudajs/cli": "*",
24
24
  "@sudajs/theme-engine": "*",
25
25
  "@types/node": "^24.13.2",
@@ -42,7 +42,7 @@
42
42
  "vite": "^8.1.0"
43
43
  },
44
44
  "peerDependencies": {
45
- "@puckeditor/core": "^0.21.3",
45
+ "@puckeditor/core": "^0.22.0",
46
46
  "@sudajs/theme-engine": "*",
47
47
  "lucide-react": "^1.17.0",
48
48
  "react": "^19.2.7",
@@ -1,11 +1,21 @@
1
1
  import type { SudaComponentConfig, SudaRootConfig } from "@sudajs/theme-engine";
2
- import { getIcpRecord, getPageSlot, getWhiteLabel } from "@sudajs/theme-engine/runtime";
2
+ import {
3
+ createThemeDesignCssVariables,
4
+ createThemeDesignDefault,
5
+ designSystemField,
6
+ getIcpRecord,
7
+ getPageSlot,
8
+ getWhiteLabel,
9
+ resolveThemeDesignTokens,
10
+ type ThemeDesignValue,
11
+ } from "@sudajs/theme-engine/runtime";
3
12
  import type { ReactElement, ReactNode } from "react";
4
13
 
5
14
  import { t } from "./i18n.js";
15
+ import { sourceManifest } from "./manifest.js";
6
16
 
7
17
  type PuckExtras = { puck?: { metadata?: Record<string, unknown> } };
8
- type RootProps = { children?: ReactNode; title?: string };
18
+ type RootProps = { children?: ReactNode; title?: string; designSystem?: ThemeDesignValue };
9
19
  type HeaderProps = { siteName?: string };
10
20
  type PageOutletProps = PuckExtras;
11
21
  type FooterProps = { text?: string } & PuckExtras;
@@ -13,13 +23,21 @@ type FooterProps = { text?: string } & PuckExtras;
13
23
  export const rootConfig: SudaRootConfig<RootProps> = {
14
24
  fields: {
15
25
  title: { type: "text", label: t("layout.root.fields.title") },
26
+ designSystem: designSystemField(t("layout.root.fields.designSystem"), sourceManifest.designSystem),
27
+ },
28
+ defaultProps: {
29
+ title: "__SUDA_THEME_KEY__",
30
+ designSystem: createThemeDesignDefault(sourceManifest.designSystem),
31
+ },
32
+ render: ({ children, designSystem, title }) => {
33
+ const tokens = resolveThemeDesignTokens(sourceManifest.designSystem, designSystem);
34
+ const style = createThemeDesignCssVariables(tokens);
35
+ return (
36
+ <div className="__SUDA_THEME_KEY__-root" data-site-name={title} style={style}>
37
+ {children}
38
+ </div>
39
+ );
16
40
  },
17
- defaultProps: { title: "__SUDA_THEME_KEY__" },
18
- render: ({ children, title }) => (
19
- <div className="__SUDA_THEME_KEY__-root" data-site-name={title}>
20
- {children}
21
- </div>
22
- ),
23
41
  };
24
42
 
25
43
  export const Header: SudaComponentConfig<HeaderProps> = {
@@ -2,7 +2,8 @@
2
2
  "layout": {
3
3
  "root": {
4
4
  "fields": {
5
- "title": "Site title"
5
+ "title": "Site title",
6
+ "designSystem": "Design system"
6
7
  }
7
8
  },
8
9
  "header": {
@@ -8,7 +8,55 @@ export const sourceManifest: ThemeSourceManifest = {
8
8
  key: "__SUDA_THEME_KEY__",
9
9
  name: "__SUDA_THEME_KEY__",
10
10
  categories: ["other"],
11
- minEngineVersion: "0.0.0",
11
+ minEngineVersion: "4.0.0",
12
+ designSystem: {
13
+ version: 1,
14
+ defaultPresetId: "default",
15
+ presets: [
16
+ {
17
+ id: "default",
18
+ label: "Default",
19
+ tokens: {
20
+ colors: {
21
+ background: "#ffffff",
22
+ foreground: "#111827",
23
+ primary: "#111827",
24
+ primaryForeground: "#ffffff",
25
+ accent: "#f8fafc",
26
+ accentForeground: "#111827",
27
+ muted: "#f4f5f7",
28
+ mutedForeground: "#4b5563",
29
+ },
30
+ radius: {
31
+ card: "8px",
32
+ button: "8px",
33
+ input: "8px",
34
+ },
35
+ },
36
+ },
37
+ {
38
+ id: "ocean",
39
+ label: "Ocean",
40
+ tokens: {
41
+ colors: {
42
+ background: "#f8fbff",
43
+ foreground: "#0e1b36",
44
+ primary: "#2563eb",
45
+ primaryForeground: "#ffffff",
46
+ accent: "#dbeafe",
47
+ accentForeground: "#0e1b36",
48
+ muted: "#eff6ff",
49
+ mutedForeground: "#42526e",
50
+ },
51
+ radius: {
52
+ card: "14px",
53
+ button: "9999px",
54
+ input: "10px",
55
+ },
56
+ },
57
+ },
58
+ ],
59
+ },
12
60
  entry: "index.js",
13
61
  clientEntry: "runtime.client.js",
14
62
  // Required. SudaCloud currently hosts SSR themes only; CSR/Hybrid postures
@@ -240,7 +240,10 @@ export const FeaturedPosts: SudaComponentConfig<FeaturedPostsProps> = {
240
240
  postList: { strategy: "featured", limit: 3 },
241
241
  },
242
242
  render: ({ id, title, description, puck }) => {
243
- const resource = getPostResource(puck?.metadata, id);
243
+ const resource = getPostResource(puck?.metadata, {
244
+ componentId: id,
245
+ fieldKey: "postList",
246
+ });
244
247
  const posts = resource.posts;
245
248
  return (
246
249
  <section className="__SUDA_THEME_KEY__-section">
@@ -3,22 +3,22 @@
3
3
 
4
4
  .__SUDA_THEME_KEY__-root {
5
5
  font-family: Inter, ui-sans-serif, system-ui, sans-serif;
6
- color: #111827;
7
- background: #ffffff;
6
+ color: var(--suda-color-foreground, #111827);
7
+ background: var(--suda-color-background, #ffffff);
8
8
  }
9
9
  .__SUDA_THEME_KEY__-header,
10
10
  .__SUDA_THEME_KEY__-footer {
11
11
  padding: 20px clamp(20px, 5vw, 64px);
12
- border-bottom: 1px solid #e5e7eb;
12
+ border-bottom: 1px solid var(--suda-color-muted, #e5e7eb);
13
13
  }
14
14
  .__SUDA_THEME_KEY__-footer {
15
15
  display: flex;
16
16
  flex-wrap: wrap;
17
17
  align-items: center;
18
18
  gap: 12px 20px;
19
- border-top: 1px solid #e5e7eb;
19
+ border-top: 1px solid var(--suda-color-muted, #e5e7eb);
20
20
  border-bottom: 0;
21
- color: #6b7280;
21
+ color: var(--suda-color-muted-foreground, #6b7280);
22
22
  }
23
23
  .__SUDA_THEME_KEY__-footer a {
24
24
  display: inline-flex;
@@ -35,19 +35,19 @@
35
35
  padding: 64px clamp(20px, 5vw, 64px);
36
36
  }
37
37
  .__SUDA_THEME_KEY__-hero {
38
- background: #f8fafc;
38
+ background: var(--suda-color-accent, #f8fafc);
39
39
  }
40
40
  .__SUDA_THEME_KEY__-hero-logo {
41
41
  width: 64px;
42
42
  height: 64px;
43
- border-radius: 14px;
43
+ border-radius: var(--suda-radius-card, 14px);
44
44
  margin-bottom: 24px;
45
45
  }
46
46
  .__SUDA_THEME_KEY__-eyebrow {
47
47
  text-transform: uppercase;
48
48
  letter-spacing: 0.08em;
49
49
  font-size: 12px;
50
- color: #2563eb;
50
+ color: var(--suda-color-primary, #2563eb);
51
51
  font-weight: 700;
52
52
  }
53
53
  .__SUDA_THEME_KEY__-section h1 {
@@ -65,16 +65,16 @@
65
65
  .__SUDA_THEME_KEY__-section p {
66
66
  max-width: 680px;
67
67
  line-height: 1.7;
68
- color: #4b5563;
68
+ color: var(--suda-color-muted-foreground, #4b5563);
69
69
  }
70
70
  .__SUDA_THEME_KEY__-button {
71
71
  display: inline-flex;
72
72
  align-items: center;
73
73
  min-height: 42px;
74
74
  padding: 0 18px;
75
- border-radius: 8px;
76
- background: #111827;
77
- color: #ffffff;
75
+ border-radius: var(--suda-radius-button, 8px);
76
+ background: var(--suda-color-primary, #111827);
77
+ color: var(--suda-color-primary-foreground, #ffffff);
78
78
  text-decoration: none;
79
79
  font-weight: 700;
80
80
  }
@@ -85,14 +85,14 @@
85
85
  margin-top: 28px;
86
86
  }
87
87
  .__SUDA_THEME_KEY__-card {
88
- border: 1px solid #e5e7eb;
89
- border-radius: 8px;
88
+ border: 1px solid var(--suda-color-muted, #e5e7eb);
89
+ border-radius: var(--suda-radius-card, 8px);
90
90
  padding: 20px;
91
91
  }
92
92
  .__SUDA_THEME_KEY__-card-icon {
93
93
  width: 24px;
94
94
  height: 24px;
95
- color: #2563eb;
95
+ color: var(--suda-color-primary, #2563eb);
96
96
  }
97
97
  .__SUDA_THEME_KEY__-quote blockquote {
98
98
  max-width: 780px;
@@ -101,15 +101,15 @@
101
101
  margin: 0 0 16px;
102
102
  }
103
103
  .__SUDA_THEME_KEY__-cta {
104
- background: #111827;
105
- color: #ffffff;
104
+ background: var(--suda-color-foreground, #111827);
105
+ color: var(--suda-color-background, #ffffff);
106
106
  }
107
107
  .__SUDA_THEME_KEY__-cta p {
108
- color: #d1d5db;
108
+ color: var(--suda-color-muted, #d1d5db);
109
109
  }
110
110
  .__SUDA_THEME_KEY__-cta .__SUDA_THEME_KEY__-button {
111
- background: #ffffff;
112
- color: #111827;
111
+ background: var(--suda-color-background, #ffffff);
112
+ color: var(--suda-color-foreground, #111827);
113
113
  }
114
114
  @media (max-width: 760px) {
115
115
  .__SUDA_THEME_KEY__-grid {