@sudajs/cli 0.9.0 → 0.9.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.9.0",
3
+ "version": "0.9.2",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "suda": "./bin/suda.js"
@@ -28,11 +28,12 @@
28
28
  "@puckeditor/core": "^0.21.3",
29
29
  "commander": "^12.1.0",
30
30
  "esbuild": "^0.25.12",
31
+ "lucide-react": "^1.17.0",
31
32
  "open": "^10.1.0",
32
33
  "react": "^19.2.7",
33
34
  "react-dom": "^19.2.7",
34
35
  "zod": "^3.24.1",
35
- "@sudajs/theme-engine": "2.3.0"
36
+ "@sudajs/theme-engine": "2.4.0"
36
37
  },
37
38
  "devDependencies": {
38
39
  "@tailwindcss/postcss": "^4.3.0",
@@ -2,42 +2,74 @@
2
2
 
3
3
  This directory is a standalone Vite + React + Tailwind Suda theme generated by `suda theme init`.
4
4
 
5
- ## Contract
5
+ Your job in a theme project is to create a complete, publishable theme with theme-specific components, realistic starter pages, scoped styling, assets, and AI metadata. Do not build pages by assembling generic engine components; those are reserved for end users who need supplemental editing blocks after the theme is installed.
6
+
7
+ ## Theme contract
6
8
 
7
9
  - Keep `renderMode: "ssr"` in `src/manifest.ts`.
8
10
  - Keep source entries at `src/index.tsx`, `src/runtime.client.ts`, and `src/styles.css`.
11
+ - Export a complete `ThemeModule` from `src/index.tsx`: `manifest`, `pageConfig`, `layoutConfig`, `defaultLayout`, and `starterPages`.
12
+ - Keep `pageConfig` focused on page content sections. Keep `layoutConfig` focused on shared site chrome such as root, header, page outlet, and footer.
9
13
  - Do not edit generated files under `dist/`; run `pnpm build` to regenerate them.
10
14
  - React, React DOM, Puck, and `@sudajs/theme-engine` are host-provided peers. Do not bundle private copies into the theme runtime.
15
+ - Keep persisted props JSON-serializable. Do not store functions, React nodes, class instances, database ids for media, or environment-specific absolute filesystem paths.
16
+
17
+ ## Component strategy
18
+
19
+ - Create theme-specific components and sections first. Every page component should express this theme's brand, industry, visual rhythm, and content model.
20
+ - Do not register `createEngineComponents()`, `createBaseBlocks()`, or `createContainers()` in `pageConfig.components` when creating a theme. Generic engine components are fallback editing tools for users, not the foundation of a theme template.
21
+ - Prefer purpose-built sections such as hero, services, product highlights, feature comparisons, gallery, process, pricing, testimonial, FAQ, article list, contact, and CTA when the theme needs those patterns.
22
+ - Keep section APIs semantic. Expose props that describe content and editor decisions, not implementation details.
23
+ - Keep `fields`, `defaultProps`, and `render` in sync. Every editor field should have a sensible default, and render functions should tolerate omitted optional values.
24
+ - Put editor controls in props when they change rendering, for example `showLogo`, `mediaPosition`, `tone`, or `columns`; pair dependent controls with `visibleIf` rather than hiding logic only in JSX.
25
+
26
+ ## Layout and slot rules
11
27
 
12
- ## Styling
28
+ - Page-level sections and components must not declare native Puck `slot` fields. Do not write `fields: { content: { type: "slot" } }` in page components.
29
+ - Native Puck slots are reserved for Suda-provided layout/container primitives and theme layout components. The normal template pattern is the one in `src/layout.tsx`: `rootConfig` renders `children`, `PageOutlet` renders `getPageSlot(puck?.metadata)`, and `Header` / `Footer` surround that outlet in `defaultLayout`.
30
+ - Do not add arbitrary nested page composition through native slots. It makes AI-generated pages unpredictable and hard to validate.
31
+ - 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.
32
+ - Do not use legacy DropZone or `zones` patterns.
33
+
34
+ ## Styling and assets
13
35
 
14
36
  - Tailwind v4 starts in `src/styles.css` with `@import "tailwindcss";` and `@source "./**/*.{ts,tsx}";`.
15
- - Theme-local assets live in top-level `assets/`; the CLI copies them to `dist/assets/` during build. Use `themeAsset("assets/...")` instead of importing images from React code.
37
+ - Theme-local assets live in top-level `assets/`; the CLI copies them to `dist/assets/` during build.
38
+ - Use `themeAsset("assets/...")` for theme-bundled assets in default props and starter pages. Do not import images from React code.
39
+ - Use `resolveAsset(puck?.metadata, value)` from `@sudajs/theme-engine/runtime` before rendering user-selected media fields.
40
+ - Scope theme CSS with the generated theme key classes. Avoid global resets that could affect the host editor or other themes.
16
41
  - 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.
17
- - Customize `vite.config.ts`, `postcss.config.mjs`, and `src/styles.css` as needed.
42
+ - Customize `vite.config.ts`, `postcss.config.mjs`, and `src/styles.css` only when the theme needs it.
18
43
 
19
- ## Commands
44
+ ## Field authoring rules
20
45
 
21
- ```bash
22
- pnpm install
23
- pnpm lint
24
- pnpm typecheck
25
- pnpm format
26
- pnpm dev # Vite-powered local preview
27
- pnpm build # Vite build + Suda artifact finalize + AI metadata check
28
- pnpm validate
29
- suda theme check
30
- ```
46
+ Use the most specific field type available. A generic `text` field may pass typecheck, but it is wrong when a semantic Suda field exists.
47
+
48
+ - Always use `url` for links and routes, including anchors, `mailto:` links, and external URLs. Do not use `text` for href-like props.
49
+ - Use `image` for images, `video` for videos, and `media` only when the same prop intentionally accepts mixed media.
50
+ - Use `color` for color values, `icon` for icon names, `font` for font selections, `range` for bounded numbers, `spacing` for spacing tokens, and `menu` for navigation items.
51
+ - Use `select` or `radio` for fixed choices. Keep option values stable, short, and serializable.
52
+ - Use `array` for repeatable items and `object` for grouped settings. Keep nested field names aligned with the prop shape and `defaultProps`.
53
+ - Use `textarea` for multi-sentence copy. Use `text` only for short labels, headings, slugs, names, or plain strings that are not URLs, media, colors, icons, fonts, or menus.
54
+ - For card grids, feature lists, pricing tables, logo walls, gallery grids, and any multi-column component, expose a `columns` prop with a `range` or `select` field and a sensible default.
55
+ - Use `visibleIf: ({ props }, { fields }) => boolean` for synchronous field visibility that depends on sibling props or resolved fields. Keep it pure and fast.
56
+ - Use Puck `resolveFields` only for heavier dynamic field changes that cannot be expressed with `visibleIf`.
57
+ - Add field-level `ai.instructions`, `ai.required`, `ai.exclude`, `ai.stream`, or `ai.bind` when a prop needs generation guidance beyond its label and type. Use `ai.stream: false` for atomic values such as URLs that should not stream partially.
31
58
 
32
59
  ## AI metadata
33
60
 
34
- Every component and layout component should declare useful `ai.instructions`. Missing component-level instructions fail `suda theme check` and block publish.
61
+ Every page component and layout component must declare useful `ai.instructions`. Missing component-level instructions fail `suda theme check` and block publish.
35
62
 
36
- Use `visibleIf: ({ props }, { fields }) => boolean` for synchronous field visibility that depends on sibling props or resolved fields. Keep it pure and fast; use Puck `resolveFields` directly for heavier dynamic field changes. AI and MCP schemas expose `visibleIf` fields as dynamic visibility so agents know the editor surface can change with props.
63
+ Good component instructions explain:
37
64
 
38
- ## Field authoring rules
65
+ - Purpose: what content or business role the component serves.
66
+ - Use when / avoid when: how AI should choose this component instead of a similar one.
67
+ - Placement: where it normally belongs in a page.
68
+ - Frequency: whether it should appear once, multiple times, or only near another section.
69
+ - Composition: required neighboring content or local blocks, when relevant.
39
70
 
40
71
  - Prefer Suda field helpers over generic text fields: use `url` for links and routes, `image` for images, `video` for videos, `media` for mixed media, `color` for colors, `icon` for icon names, `font` for font selections, `range` for bounded numbers, `spacing` for spacing tokens, and `menu` for navigation items.
72
+ - For icons, expose props with `{ type: "icon" }`, type values as `SudaLucideIconName`, render them with `SudaLucideIcon` from `@sudajs/theme-engine/runtime`, and store canonical Lucide names such as `"rocket"` or `"mouse-pointer-click"` (not `"lucide-rocket"`). Do not keep theme-local icon maps, emoji/icon switch statements, or custom SVG icon registries unless the theme truly needs a bespoke non-Lucide graphic.
41
73
  - Use `select` or `radio` when authors must choose from a fixed set of values. Keep option values stable and serializable.
42
74
  - Use `array` for repeatable items and `object` for grouped settings. Keep nested field names aligned with the prop shape and `defaultProps`.
43
75
  - For card grids, feature lists, pricing tables, logo walls, gallery grids, and any multi-column component, expose a `columns` prop with a `range` or `select` field and a sensible default.
@@ -46,13 +78,15 @@ Use `visibleIf: ({ props }, { fields }) => boolean` for synchronous field visibi
46
78
  - Put editor-only controls in props when they change rendering, for example `showLogo`; pair dependent controls with `visibleIf` rather than hiding logic only in JSX.
47
79
  - Keep persisted props JSON-serializable. Do not store functions, React nodes, class instances, database ids for media, or environment-specific absolute filesystem paths.
48
80
  - Add optional field-level `ai.instructions`, `ai.required`, or `ai.exclude` when a prop needs generation guidance beyond its label and type.
81
+ Use `ai.exclude: true` only for structural or editor-only components, and still provide instructions explaining why AI must not generate them. `PageOutlet` is the standard example.
49
82
 
50
83
  ## Block slot authoring rules
51
84
 
52
- Prefer `blockSlots` when a section owns nested content that benefits from independent editor selection and configuration, such as hero actions, pricing cards, feature rows, stats, or timeline items. This is usually more editor-friendly than modeling those items as plain `array` / `object` fields because each local block can be selected and configured on its own in the visual editor. Keep ordinary section props in `fields`; `fields` is optional and does not need to list every prop.
85
+ Use `blockSlots` when a section owns controlled local content that benefits from independent editor selection and configuration, such as hero actions, pricing cards, feature rows, stats, timeline items, or contact methods. Keep ordinary section props in `fields`.
53
86
 
54
- - Type the slot prop as Puck `Slot` and declare `blockSlots` with the same prop key. Non-slot props should stay in `fields` or nested `array` / `object` fields.
87
+ - Type the slot prop as Puck `Slot` and declare `blockSlots` with the same prop key. This is the Suda block-slot API; do not create a native `fields.<key>.type = "slot"` yourself.
55
88
  - Define local block kinds under `blockSlots.<slotName>.blocks`. Local blocks support normal Suda fields, `label`, `defaultProps`, `render`, `metadata`, `inline`, `permissions`, and optional `ai`.
89
+ - Keep local blocks specific to the owning section. They are not reusable global page components.
56
90
  - Do not add nested `blockSlots` or `cms` to local blocks. Block slots are one level deep in the theme authoring API.
57
91
  - Do not put the slot prop in the host component's `defaultProps`; use `defaultBlocks` on the slot instead.
58
92
  - Do not hand-write `id` in local block `defaultProps`, `defaultBlocks[].props`, or starter page nested block props. The editor/runtime owns block ids.
@@ -71,7 +105,8 @@ type HeroProps = {
71
105
  export const Hero: SudaComponentConfig<HeroProps> = {
72
106
  label: "Hero",
73
107
  ai: {
74
- instructions: "Primary page introduction with editable call-to-action blocks.",
108
+ instructions:
109
+ "Primary page introduction for landing pages. Use once near the top with one or two local action blocks.",
75
110
  },
76
111
  fields: {
77
112
  title: { type: "text", label: "Title" },
@@ -108,7 +143,7 @@ export const Hero: SudaComponentConfig<HeroProps> = {
108
143
  };
109
144
  ```
110
145
 
111
- Starter page data should use the public host component type and a standard slot array on the slot prop. Use local block `kind` values as nested `type` values.
146
+ Starter page data should use the public host component type and a standard array on the block-slot prop. Use local block `kind` values as nested `type` values.
112
147
 
113
148
  ```ts
114
149
  {
@@ -128,3 +163,35 @@ Starter page data should use the public host component type and a standard slot
128
163
  },
129
164
  }
130
165
  ```
166
+
167
+ ## Starter pages
168
+
169
+ - Provide realistic starter pages in `src/templates.ts`; do not ship placeholder-only pages.
170
+ - Every starter page is Puck `Data` with `root: { props: {} }` and `content: [...]`.
171
+ - Each top-level component instance in starter page `content` must include a stable `props.id`.
172
+ - Use only components registered in `pageConfig.components` for top-level page content.
173
+ - 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.
174
+ - Use `themeAsset("assets/...")` for bundled starter media.
175
+
176
+ ## CMS-aware themes
177
+
178
+ - If the theme supports posts or tags, declare `content.views` on the exported `ThemeModule`.
179
+ - Add one starter page with matching `cmsView` for every declared CMS view.
180
+ - Declare component-level `cms` capabilities only on components that can render that CMS source.
181
+ - Use `cmsBinding` in starter page props when a component should receive CMS data at render time.
182
+ - Keep static fallback props for CMS components so the page still renders meaningfully when CMS payloads are absent.
183
+
184
+ ## Commands
185
+
186
+ ```bash
187
+ pnpm install
188
+ pnpm lint
189
+ pnpm typecheck
190
+ pnpm format
191
+ pnpm dev # Vite-powered local preview
192
+ pnpm build # Vite build + Suda artifact finalize + AI metadata check
193
+ pnpm validate
194
+ suda theme check
195
+ ```
196
+
197
+ Before publishing or handing off a finished theme, run `pnpm typecheck`, `pnpm lint`, `pnpm build`, and `pnpm validate`. Use `suda theme check` when iterating on AI metadata.
@@ -31,6 +31,7 @@
31
31
  "eslint-plugin-react-hooks": "^7.1.1",
32
32
  "eslint-plugin-react-refresh": "^0.4.26",
33
33
  "globals": "^16.5.0",
34
+ "lucide-react": "^1.17.0",
34
35
  "postcss": "^8.5.15",
35
36
  "prettier": "^3.8.4",
36
37
  "react": "^19.2.7",
@@ -43,6 +44,7 @@
43
44
  "peerDependencies": {
44
45
  "@puckeditor/core": "^0.21.3",
45
46
  "@sudajs/theme-engine": "*",
47
+ "lucide-react": "^1.17.0",
46
48
  "react": "^19.2.7",
47
49
  "react-dom": "^19.2.7"
48
50
  }
@@ -1,9 +1,9 @@
1
- import type { SudaComponentConfig } from "@sudajs/theme-engine";
2
- import { resolveAsset, type ThemeRenderMetadata } from "@sudajs/theme-engine/runtime";
1
+ import type { SudaComponentConfig, SudaLucideIconName } from "@sudajs/theme-engine";
2
+ import { resolveAsset, SudaLucideIcon, type ThemeRenderMetadata } from "@sudajs/theme-engine/runtime";
3
3
 
4
4
  import { themeAsset } from "./theme-asset.js";
5
5
 
6
- type FeatureItem = { title?: string; description?: string };
6
+ type FeatureItem = { icon?: SudaLucideIconName; title?: string; description?: string };
7
7
  type PuckExtras = { puck?: { metadata?: ThemeRenderMetadata } };
8
8
  type HeroProps = {
9
9
  showLogo?: boolean;
@@ -114,6 +114,7 @@ export const FeatureGrid: SudaComponentConfig<FeatureGridProps> = {
114
114
  label: "Features",
115
115
  arrayFields: {
116
116
  title: { type: "text", label: "Title" },
117
+ icon: { type: "icon", label: "Icon" },
117
118
  description: { type: "textarea", label: "Description" },
118
119
  },
119
120
  },
@@ -123,9 +124,9 @@ export const FeatureGrid: SudaComponentConfig<FeatureGridProps> = {
123
124
  description: "Use this section to explain the core value of the project.",
124
125
  columns: 3,
125
126
  features: [
126
- { title: "Fast setup", description: "Start from a clean theme contract." },
127
- { title: "Visual editing", description: "Expose content fields through Puck." },
128
- { title: "Publish ready", description: "Build and publish with the Suda CLI." },
127
+ { icon: "rocket", title: "Fast setup", description: "Start from a clean theme contract." },
128
+ { icon: "mouse-pointer-click", title: "Visual editing", description: "Expose content fields through Puck." },
129
+ { icon: "send", title: "Publish ready", description: "Build and publish with the Suda CLI." },
129
130
  ],
130
131
  },
131
132
  render: ({ title, description, columns = 3, features = [] }) => (
@@ -138,6 +139,7 @@ export const FeatureGrid: SudaComponentConfig<FeatureGridProps> = {
138
139
  >
139
140
  {features.map((feature: FeatureItem, index: number) => (
140
141
  <article className="__SUDA_THEME_KEY__-card" key={index}>
142
+ <SudaLucideIcon className="__SUDA_THEME_KEY__-card-icon" name={feature.icon} />
141
143
  <h3>{feature.title}</h3>
142
144
  <p>{feature.description}</p>
143
145
  </article>
@@ -89,6 +89,11 @@
89
89
  border-radius: 8px;
90
90
  padding: 20px;
91
91
  }
92
+ .__SUDA_THEME_KEY__-card-icon {
93
+ width: 24px;
94
+ height: 24px;
95
+ color: #2563eb;
96
+ }
92
97
  .__SUDA_THEME_KEY__-quote blockquote {
93
98
  max-width: 780px;
94
99
  font-size: 28px;