@sudajs/cli 0.13.1 → 0.13.3
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 +2 -2
- package/templates/theme/AGENTS.md +393 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sudajs/cli",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.3",
|
|
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.3"
|
|
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" },
|
|
@@ -485,6 +644,238 @@ export const Hero: SudaComponentConfig<HeroProps> = {
|
|
|
485
644
|
- 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.
|
|
486
645
|
- Do not use legacy DropZone or `zones` patterns.
|
|
487
646
|
|
|
647
|
+
## Puck Editor Compatibility Contract
|
|
648
|
+
|
|
649
|
+
Every public page and layout component must render consistently in the public
|
|
650
|
+
site, the Puck Editor iframe, and editor preview mode. Build this compatibility
|
|
651
|
+
in when creating the theme; do not wait for an editor-only positioning bug.
|
|
652
|
+
|
|
653
|
+
### Puck mutates the real component root
|
|
654
|
+
|
|
655
|
+
In edit mode Puck may attach selection and drag attributes and an inline
|
|
656
|
+
position directly to the first real DOM element returned by a component:
|
|
657
|
+
|
|
658
|
+
```ts
|
|
659
|
+
el.setAttribute("data-puck-component", id);
|
|
660
|
+
el.setAttribute("data-puck-dnd", id);
|
|
661
|
+
el.style.position = "relative";
|
|
662
|
+
```
|
|
663
|
+
|
|
664
|
+
- Never assume `[data-puck-component]` is an external wrapper. It may be the
|
|
665
|
+
component's own `<nav>`, `<header>`, `<section>`, or `<footer>`.
|
|
666
|
+
- `:has(.component-root)` does not match an element that is itself
|
|
667
|
+
`.component-root`. When both DOM shapes are possible, cover both explicitly:
|
|
668
|
+
|
|
669
|
+
```css
|
|
670
|
+
.component-root[data-puck-component],
|
|
671
|
+
[data-puck-component]:has(.component-root) {
|
|
672
|
+
/* compatibility override */
|
|
673
|
+
}
|
|
674
|
+
```
|
|
675
|
+
|
|
676
|
+
- Puck's inline `position: relative` beats ordinary theme CSS. A component
|
|
677
|
+
whose real root must remain `sticky`, `fixed`, or `absolute` in the editor
|
|
678
|
+
needs a specific editor-scoped rule with `!important`.
|
|
679
|
+
- Never apply `position: relative !important` to every editor component. That
|
|
680
|
+
destroys navigation, overlays, and floating controls.
|
|
681
|
+
|
|
682
|
+
### Stable component roots
|
|
683
|
+
|
|
684
|
+
- Every public page and layout component should return one stable, semantic,
|
|
685
|
+
real DOM root. Prefer `<header>` or `<nav>` for navigation, `<footer>` for the
|
|
686
|
+
footer, and `<section>` or an explicit container for ordinary sections.
|
|
687
|
+
- Do not use a Fragment as the root of a component that controls positioning,
|
|
688
|
+
margin, size, stacking, background, or editor selection. Do not depend on
|
|
689
|
+
Puck to wrap a Fragment.
|
|
690
|
+
- Do not use `display: contents` on a root that needs margin, padding,
|
|
691
|
+
positioning, sticky/fixed behavior, `z-index`, background, or a selection
|
|
692
|
+
outline.
|
|
693
|
+
- `PageOutlet` must render a real DOM element when it needs negative margin,
|
|
694
|
+
overlap, positioning, or sizing.
|
|
695
|
+
|
|
696
|
+
### Explicit editor scope
|
|
697
|
+
|
|
698
|
+
The layout root must derive editor state from
|
|
699
|
+
`puck?.metadata?.isEditor === true` and expose a stable marker:
|
|
700
|
+
|
|
701
|
+
```tsx
|
|
702
|
+
<div className="theme-root" data-suda-editor={isEditor ? "true" : undefined}>
|
|
703
|
+
{children}
|
|
704
|
+
</div>
|
|
705
|
+
```
|
|
706
|
+
|
|
707
|
+
Keep every editor-only compatibility rule below
|
|
708
|
+
`.theme-root[data-suda-editor="true"]`. Do not globally override `.navbar`,
|
|
709
|
+
`.section`, or `[data-puck-component]`, change the public-site layout to repair
|
|
710
|
+
the editor, use vague selectors that affect unrelated components, or edit
|
|
711
|
+
third-party minified CSS. Put compatibility overrides in the theme's scoped
|
|
712
|
+
`src/styles.css`.
|
|
713
|
+
|
|
714
|
+
When the component itself may be the Puck root or may sit inside one, write and
|
|
715
|
+
test both selectors. Keep later rules from overriding the correction:
|
|
716
|
+
|
|
717
|
+
```css
|
|
718
|
+
.theme-root[data-suda-editor="true"] .theme-navigation[data-puck-component],
|
|
719
|
+
.theme-root[data-suda-editor="true"]
|
|
720
|
+
[data-puck-component]:has(.theme-navigation)
|
|
721
|
+
.theme-navigation {
|
|
722
|
+
position: sticky !important;
|
|
723
|
+
top: 0;
|
|
724
|
+
z-index: 30;
|
|
725
|
+
}
|
|
726
|
+
```
|
|
727
|
+
|
|
728
|
+
### Sticky and fixed positioning
|
|
729
|
+
|
|
730
|
+
Before implementing Header, announcement bar, floating CTA, or any
|
|
731
|
+
sticky/fixed component, verify all of the following:
|
|
732
|
+
|
|
733
|
+
- whether Puck writes `position: relative` on the actual positioned root;
|
|
734
|
+
- which element really scrolls in the site and in the editor iframe;
|
|
735
|
+
- that the sticky element is inside the intended scroll container;
|
|
736
|
+
- whether any ancestor has `overflow: hidden`, `auto`, `scroll`, or `clip`, or
|
|
737
|
+
has `transform`, `filter`, `perspective`, or `contain`;
|
|
738
|
+
- that the sticky parent is taller than the sticky element where required;
|
|
739
|
+
- that `top` and `z-index` are explicit; and
|
|
740
|
+
- that iframe scrolling and the outer editor canvas do not use incompatible
|
|
741
|
+
coordinate systems.
|
|
742
|
+
|
|
743
|
+
For a layout Header, prefer putting sticky behavior on the Header's real root.
|
|
744
|
+
If that root receives `data-puck-component`, override Puck's inline position on
|
|
745
|
+
that same node. Do not force the Header root to `relative !important` and then
|
|
746
|
+
expect a parent sticky rule to work. Site and editor may use different
|
|
747
|
+
positioning strategies only when their visual result remains equivalent.
|
|
748
|
+
|
|
749
|
+
### Transparent navigation is state plus layout
|
|
750
|
+
|
|
751
|
+
Transparent navigation must coordinate all of these concerns, not merely set
|
|
752
|
+
`background: transparent`:
|
|
753
|
+
|
|
754
|
+
- transparent background and removed shadow;
|
|
755
|
+
- logo, menu text, and button color changes;
|
|
756
|
+
- Hero or `PageOutlet` overlap beneath the Header;
|
|
757
|
+
- restoration of the solid state after the scroll threshold;
|
|
758
|
+
- Puck's root-node positioning mutation in the editor; and
|
|
759
|
+
- identical SSR-first-frame and hydrated state.
|
|
760
|
+
|
|
761
|
+
Never render a white logo/text state above an accidentally white navigation
|
|
762
|
+
background. Prefer React Context for a page's transparent-navigation request,
|
|
763
|
+
an SSR-readable DOM marker for the first-frame fallback, and a real
|
|
764
|
+
`PageOutlet` element with negative margin in the editor. The public site may use
|
|
765
|
+
a spacer or equivalent layout strategy. Keep shared measurements in CSS
|
|
766
|
+
variables, for example `--theme-navigation-height: 4.6rem`.
|
|
767
|
+
|
|
768
|
+
### Editor scroll detection
|
|
769
|
+
|
|
770
|
+
Do not rely only on `window.scrollY` or a `window` scroll listener. The active
|
|
771
|
+
scroll source may be `document.scrollingElement`, the iframe document, or an
|
|
772
|
+
inner `overflow: auto/scroll` container, and `scroll` does not bubble.
|
|
773
|
+
|
|
774
|
+
- Find the nearest scrollable ancestor for each navigation instance and read
|
|
775
|
+
that element's own `scrollTop`.
|
|
776
|
+
- Listen on the document with
|
|
777
|
+
`document.addEventListener("scroll", handler, { capture: true, passive: true })`
|
|
778
|
+
and keep a window listener for public-site compatibility.
|
|
779
|
+
- Do not assume editor and site share the same scrolling element.
|
|
780
|
+
|
|
781
|
+
### React and editor runtime lifecycle
|
|
782
|
+
|
|
783
|
+
Runtime code must tolerate editor remounts, live prop changes, HMR, frequent
|
|
784
|
+
`MutationObserver` callbacks, React Strict Mode effect replay, and loading after
|
|
785
|
+
`DOMContentLoaded` has already fired.
|
|
786
|
+
|
|
787
|
+
- Make event binding and initialization idempotent.
|
|
788
|
+
- Observe components inserted later and relevant attribute changes.
|
|
789
|
+
- Do not use a permanent `window.__xxxBound` flag that prevents required HMR
|
|
790
|
+
reinitialization.
|
|
791
|
+
- Clean up component effects, observers, and listeners.
|
|
792
|
+
- Keep SSR markers and client state synchronized instead of letting them fight.
|
|
793
|
+
|
|
794
|
+
### Puck interaction styles
|
|
795
|
+
|
|
796
|
+
Assume edit mode may apply rules equivalent to:
|
|
797
|
+
|
|
798
|
+
```css
|
|
799
|
+
[data-puck-component] * {
|
|
800
|
+
pointer-events: none;
|
|
801
|
+
user-select: none;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
[data-puck-component] {
|
|
805
|
+
pointer-events: auto !important;
|
|
806
|
+
}
|
|
807
|
+
```
|
|
808
|
+
|
|
809
|
+
An unclickable link, dropdown, carousel, or button in edit mode is not by
|
|
810
|
+
itself a theme interaction bug. Distinguish edit mode from preview mode. Never
|
|
811
|
+
globally restore descendant pointer events because that breaks selection and
|
|
812
|
+
dragging; test visitor interactions in editor preview and the public site.
|
|
813
|
+
|
|
814
|
+
### Layout ownership
|
|
815
|
+
|
|
816
|
+
- Keep only `Header`, `PageOutlet`, `Footer`, and necessary global chrome such
|
|
817
|
+
as an announcement bar in `layoutConfig`.
|
|
818
|
+
- Render Header and Footer exactly once per page from the layout. Do not repeat
|
|
819
|
+
them in starter pages.
|
|
820
|
+
- If Footer or Header moves from page sections into the layout, remove duplicate
|
|
821
|
+
entries from `starterPages`, `cmsTemplates`, `pageConfig.categories`, and AI
|
|
822
|
+
examples.
|
|
823
|
+
- Give Header, `PageOutlet`, and Footer stable `props.id` values, with
|
|
824
|
+
`PageOutlet` between Header and Footer.
|
|
825
|
+
- Read platform white-label, ICP, and other footer metadata from runtime
|
|
826
|
+
metadata; do not persist it as duplicated static component data.
|
|
827
|
+
|
|
828
|
+
### Vendor CSS
|
|
829
|
+
|
|
830
|
+
Do not modify original third-party minified CSS unless the task explicitly
|
|
831
|
+
requires a maintained vendor patch. Use precise selectors in the theme's own
|
|
832
|
+
scoped `src/styles.css`. If a vendor file truly must change, document the
|
|
833
|
+
reproducible problem and why an override cannot solve it.
|
|
834
|
+
|
|
835
|
+
### New-theme completion checklist
|
|
836
|
+
|
|
837
|
+
When creating a theme, complete all of these in the initial implementation:
|
|
838
|
+
|
|
839
|
+
- editor root marker and stable real roots;
|
|
840
|
+
- layout-owned Header / `PageOutlet` / Footer with stable ids;
|
|
841
|
+
- Header sticky compatibility in the editor;
|
|
842
|
+
- transparent navigation in editor and site modes;
|
|
843
|
+
- scrollable-ancestor detection and an SSR state marker;
|
|
844
|
+
- idempotent runtime setup and cleanup;
|
|
845
|
+
- real-element `PageOutlet` overlap behavior;
|
|
846
|
+
- responsive viewport behavior; and
|
|
847
|
+
- editor preview versus public-site comparison.
|
|
848
|
+
|
|
849
|
+
### Required regression matrix
|
|
850
|
+
|
|
851
|
+
Whenever Header, transparent navigation, sticky/fixed positioning,
|
|
852
|
+
`PageOutlet`, Footer, carousel, dropdown/mobile menu, or a `100vh` Hero changes,
|
|
853
|
+
verify:
|
|
854
|
+
|
|
855
|
+
- public-site SSR first frame and post-hydration state;
|
|
856
|
+
- editor desktop, tablet, and mobile viewports;
|
|
857
|
+
- editor edit mode and preview mode;
|
|
858
|
+
- initial top state and state after crossing the scroll threshold;
|
|
859
|
+
- state after HMR and live prop updates;
|
|
860
|
+
- exactly one Header and one Footer; and
|
|
861
|
+
- intact Puck selection and drag behavior.
|
|
862
|
+
|
|
863
|
+
### Debugging order
|
|
864
|
+
|
|
865
|
+
When editor and site differ, inspect in this order before changing CSS:
|
|
866
|
+
|
|
867
|
+
1. The component's actual root node.
|
|
868
|
+
2. The element that received `data-puck-component`.
|
|
869
|
+
3. Inline styles written on that element.
|
|
870
|
+
4. Computed `position`, `background`, `z-index`, and ancestor `overflow`.
|
|
871
|
+
5. The real scrolling element and its `scrollTop`.
|
|
872
|
+
6. Iframe versus outer-canvas coordinate systems.
|
|
873
|
+
7. Whether the theme runtime initialized.
|
|
874
|
+
8. Transparent-state Context and DOM markers.
|
|
875
|
+
9. Stylesheet load and cascade order.
|
|
876
|
+
|
|
877
|
+
Do not guess selectors repeatedly before confirming the DOM and computed style.
|
|
878
|
+
|
|
488
879
|
## Design system rules
|
|
489
880
|
|
|
490
881
|
Build every theme from one theme-level design system. Do not let each section,
|