@sudajs/cli 0.18.9 → 0.18.10

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.
@@ -0,0 +1,270 @@
1
+ # Component Authoring
2
+
3
+ Read this guide before changing sections, fields, defaults, navigation data,
4
+ icons, AI metadata, or block slots. The root `AGENTS.md` rules still apply.
5
+
6
+ ## Naming and organization
7
+
8
+ - Group editor components by page role when using `pageConfig.categories`.
9
+ Category titles, component labels, and field labels must use `t(...)`.
10
+ - Use short, concrete PascalCase public keys that describe visitor-facing
11
+ patterns: `HeroImage`, `FeatureCards`, `PricingTable`, `ContactUs`.
12
+ - Avoid theme names, implementation details, and filler suffixes such as
13
+ `Section`, `Component`, `Block`, `New`, or `Custom`.
14
+ - Prefer semantic variants such as `FeatureCards` and `FeatureList`. Numbered
15
+ variants are a fallback and need distinct `ai.instructions` explaining their
16
+ visual or business difference.
17
+ - A top-level `content[].type` is a public component key. A concrete instance's
18
+ `content[].props.id` is separate and must be stable.
19
+ - Keep one page section per kebab-case `.tsx` file under `src/sections/`, with
20
+ a small `src/sections/index.ts` registry/export module.
21
+
22
+ ## Field source of truth
23
+
24
+ Type props first. Use `SudaComponentConfig<Props>`, `SudaFields<Props>`, and
25
+ `defineSudaPageData(pageConfig, data)` to catch shape mismatches. Do not invent a
26
+ parallel Zod schema for theme fields.
27
+
28
+ Keep these four structures aligned:
29
+
30
+ 1. TypeScript props.
31
+ 2. `fields` or local-block fields.
32
+ 3. `defaultProps`.
33
+ 4. `render`.
34
+
35
+ Every normal field needs a matching serializable value in `defaultProps`.
36
+ Block-slot props are the exception: their initial composition belongs in the
37
+ slot's `defaultBlocks`, not the host component's `defaultProps`.
38
+
39
+ All normal prop defaults belong only in `defaultProps`. Starter/CMS data is
40
+ authored instance content, not component defaults. Render props directly; do
41
+ not use `||`, `??`, ternaries, default parameters, or destructuring defaults to
42
+ invent display values. Conditional rendering may remove intentionally optional
43
+ UI, but must not substitute content.
44
+
45
+ ## Field selection
46
+
47
+ Use the most specific supported field:
48
+
49
+ - `text`: short labels, headings, slugs, and names.
50
+ - `textarea`: multi-sentence plain copy.
51
+ - `richtext`: authored rich HTML content.
52
+ - `url`: routes, anchors, `mailto:`, and external links.
53
+ - `image`, `video`, `media`: media according to the accepted kind.
54
+ - `icon`: canonical Suda icon names.
55
+ - `color`, `range`, `spacing`: editor-facing design values.
56
+ - `number`, `checkbox`: numeric and boolean values.
57
+ - `select`, `radio`: stable serializable choices.
58
+ - `object`, `array`: grouped and repeatable structured values.
59
+ - `posts`: one dynamic post query on an ordinary page section.
60
+
61
+ Rules:
62
+
63
+ - Never use `text` for URLs, media, colors, icons, or structured navigation.
64
+ - Use localized option labels and short stable option values.
65
+ - Add localized labels to nested `arrayFields`, `objectFields`, and local blocks.
66
+ - Add `description` and `placeholder` only when they help editor decisions.
67
+ - Use `visibleIf` for pure synchronous sibling-dependent visibility. Reserve
68
+ `resolveFields` for heavier changes that `visibleIf` cannot express.
69
+ - Add `ai.instructions`, `ai.required`, `ai.exclude`, `ai.stream`, or `ai.bind`
70
+ when a field needs generation guidance. Set `ai.stream: false` for atomic
71
+ values such as URLs.
72
+ - Multi-column grids, lists, pricing tables, galleries, and logo walls expose a
73
+ `columns` prop with a `range` or `select` field and a sensible default.
74
+
75
+ Representative field configuration:
76
+
77
+ ```ts
78
+ fields: {
79
+ title: { type: "text", label: t("common.fields.title") },
80
+ body: { type: "textarea", label: t("common.fields.description") },
81
+ href: { type: "url", label: t("common.fields.link") },
82
+ image: { type: "image", label: t("common.fields.image") },
83
+ icon: { type: "icon", label: t("common.fields.icon") },
84
+ columns: {
85
+ type: "range",
86
+ label: t("common.fields.columns"),
87
+ min: 2,
88
+ max: 4,
89
+ step: 1,
90
+ },
91
+ align: {
92
+ type: "radio",
93
+ label: t("common.fields.align"),
94
+ options: [
95
+ { label: t("common.options.left"), value: "left" },
96
+ { label: t("common.options.center"), value: "center" },
97
+ ],
98
+ },
99
+ cards: {
100
+ type: "array",
101
+ label: t("common.fields.cards"),
102
+ defaultItemProps: { title: "Card title", href: "/" },
103
+ arrayFields: {
104
+ title: { type: "text", label: t("common.fields.title") },
105
+ href: { type: "url", label: t("common.fields.link") },
106
+ },
107
+ },
108
+ },
109
+ defaultProps: {
110
+ title: "Welcome",
111
+ body: "Describe the offer.",
112
+ href: "/contact",
113
+ image: themeAsset("assets/hero.jpg"),
114
+ icon: "sparkles",
115
+ columns: 3,
116
+ align: "left",
117
+ cards: [{ title: "Fast setup", href: "/features" }],
118
+ },
119
+ ```
120
+
121
+ ## Navigation and footer menus
122
+
123
+ Navigation and footer menus are typed arrays. Do not use a legacy menu field,
124
+ textarea, or newline-delimited string. Every destination uses a `url` field.
125
+ TypeScript props, fields, `defaultItemProps`, and component `defaultProps` must
126
+ have exactly the same structure.
127
+
128
+ Navigation supports at most two levels:
129
+
130
+ - `navItems[]`
131
+ - `navItems[].submenu[]`
132
+
133
+ Submenu items contain only label/link fields. Their schema must make a third
134
+ level impossible. An empty `submenu` means an ordinary top-level link.
135
+
136
+ ```ts
137
+ type NavItem = {
138
+ label: string;
139
+ href: string;
140
+ submenu: { label: string; href: string }[];
141
+ };
142
+
143
+ const navItemsField = {
144
+ type: "array",
145
+ label: t("common.fields.navigationItems"),
146
+ defaultItemProps: { label: "Home", href: "/", submenu: [] },
147
+ arrayFields: {
148
+ label: { type: "text", label: t("common.fields.label") },
149
+ href: { type: "url", label: t("common.fields.link") },
150
+ submenu: {
151
+ type: "array",
152
+ label: t("common.fields.submenu"),
153
+ defaultItemProps: { label: "About", href: "/about" },
154
+ arrayFields: {
155
+ label: { type: "text", label: t("common.fields.label") },
156
+ href: { type: "url", label: t("common.fields.link") },
157
+ },
158
+ },
159
+ },
160
+ };
161
+ ```
162
+
163
+ Footer grouped menus use exactly two array levels: `columns[]` and
164
+ `columns[].links[]`. A column has a title and links; a link has text and URL.
165
+ Do not add another nested group.
166
+
167
+ ## Icons
168
+
169
+ Interaction and UI-state icons may be fixed when their identity is intrinsic to
170
+ the control: menu, close, previous/next, expand/collapse, loading.
171
+
172
+ Icons that communicate authored content are props:
173
+
174
+ - Declare `{ type: "icon" }` and type values as `SudaIconName`.
175
+ - Put initial icon selections in `defaultProps`, including nested array/object
176
+ values.
177
+ - Render the value with `SudaIcon` from `@sudajs/theme-engine/icons`.
178
+ - Do not hardcode content icon names or icon components inside `render`.
179
+
180
+ Use canonical Lucide names such as `"rocket"` or `"mouse-pointer-click"`, not
181
+ `"lucide-rocket"`. Use namespaced Simple Icons brand names such as
182
+ `"simple-icons:github"`. The engine supports Lucide Icons and Simple Icons; do
183
+ not add another icon library unless neither can represent a required icon. Do
184
+ not create theme-local icon maps, emoji switches, SVG registries, or custom icon
185
+ systems unless the theme genuinely needs a bespoke graphic.
186
+
187
+ ## AI metadata
188
+
189
+ Every page and layout component declares useful `ai.instructions`. Missing
190
+ component-level instructions fail `suda theme check` and block publishing.
191
+ Instructions should cover:
192
+
193
+ - purpose and business role;
194
+ - use/avoid conditions;
195
+ - normal placement;
196
+ - expected frequency;
197
+ - composition or neighboring sections when relevant.
198
+
199
+ Use `ai.exclude: true` only for structural/editor-only components, and explain
200
+ why AI must not generate them. `PageOutlet` is the standard example.
201
+
202
+ ## Block slots
203
+
204
+ Use `blockSlots` when a section owns controlled local content such as actions,
205
+ pricing cards, feature rows, stats, timeline items, or contact methods.
206
+
207
+ - Type the slot prop as Puck `Slot` and use the same prop key in `blockSlots`.
208
+ - Do not create native `fields.<key>.type = "slot"` entries.
209
+ - Define allowed local kinds under `blockSlots.<slotName>.blocks`.
210
+ - Every local block needs a localized `label`, typed fields, `defaultProps`,
211
+ `render`, and useful AI instructions when applicable.
212
+ - Local blocks belong only to their owning section and cannot contain nested
213
+ block slots.
214
+ - Initial composition belongs in `defaultBlocks`; do not put the slot prop in
215
+ host `defaultProps`.
216
+ - Never put `id` in local block `defaultProps`, `defaultBlocks[].props`, or
217
+ authored nested items. The editor/runtime owns local ids.
218
+
219
+ ```tsx
220
+ type HeroProps = { title?: string; actions?: Slot };
221
+
222
+ export const Hero: SudaComponentConfig<HeroProps> = {
223
+ label: t("sections.hero.label"),
224
+ ai: { instructions: "Primary landing-page introduction. Use once near the top." },
225
+ fields: {
226
+ title: { type: "text", label: t("sections.hero.fields.title") },
227
+ },
228
+ blockSlots: {
229
+ actions: {
230
+ label: t("sections.hero.blocks.actions"),
231
+ blocks: {
232
+ button: {
233
+ label: t("sections.hero.blocks.button"),
234
+ fields: {
235
+ label: { type: "text", label: t("sections.hero.blocks.buttonLabel") },
236
+ href: { type: "url", label: t("sections.hero.blocks.buttonHref") },
237
+ },
238
+ defaultProps: { label: "Get started", href: "/contact" },
239
+ render: ({ label, href }) => <a href={href}>{label}</a>,
240
+ },
241
+ },
242
+ defaultBlocks: [{ kind: "button" }],
243
+ },
244
+ },
245
+ defaultProps: { title: "Build with SudaCloud" },
246
+ render: ({ title, actions: Actions }) => (
247
+ <section>
248
+ <h1>{title}</h1>
249
+ <Actions />
250
+ </section>
251
+ ),
252
+ };
253
+ ```
254
+
255
+ Authored page data uses the public host type and short local kind:
256
+
257
+ ```ts
258
+ {
259
+ type: "Hero",
260
+ props: {
261
+ id: "Hero-1",
262
+ title: "Welcome",
263
+ actions: [{ type: "button", props: { label: "Contact us", href: "/contact" } }],
264
+ },
265
+ }
266
+ ```
267
+
268
+ Never write internal types such as
269
+ `__suda_local_block__/Hero/actions/button`; those are generated only for Puck
270
+ internals.
@@ -0,0 +1,170 @@
1
+ # Design and Runtime
2
+
3
+ Read this guide before changing the theme contract, manifest, layout, design
4
+ tokens, CSS, assets, contact forms, or runtime metadata. The root `AGENTS.md`
5
+ rules still apply.
6
+
7
+ ## Theme contract
8
+
9
+ - Keep `renderMode: "ssr"` in `src/manifest.ts`.
10
+ - Keep source entries at `src/index.tsx` and `src/styles.css`.
11
+ - Export a complete `ThemeModule`: `manifest`, `pageConfig`, `layoutConfig`,
12
+ `defaultLayout`, `starterPages`, and `cmsTemplates`.
13
+ - Keep page sections in `pageConfig`; keep the root, Header, PageOutlet, Footer,
14
+ and necessary global chrome in `layoutConfig`.
15
+ - If browser-only setup is required, export `clientHooks` from `src/client.ts`.
16
+ Do not export theme config there and do not add `src/runtime.client.ts(x)`;
17
+ the CLI generates `dist/runtime.client.js`.
18
+ - React, React DOM, Puck, and `@sudajs/theme-engine` are host-provided peers.
19
+ Do not bundle private copies into the runtime.
20
+ - Keep persisted props JSON-serializable. Never persist functions, React nodes,
21
+ class instances, media database ids, or absolute local paths.
22
+ - Do not edit generated files under `dist/`.
23
+
24
+ Starter routes in `suda theme dev` are site-like root routes such as `/index`
25
+ and `/contact-us`, never `/pages/...`.
26
+
27
+ ## Layout
28
+
29
+ - Render Header and Footer exactly once from `defaultLayout`, with PageOutlet
30
+ between them and stable top-level ids.
31
+ - Do not include layout chrome in starter pages or CMS templates.
32
+ - Root config renders layout children. PageOutlet reads the page slot through
33
+ `getPageSlot(puck?.metadata)`.
34
+ - Native Puck slots are limited to engine layout/container primitives and theme
35
+ layout components. Page sections use Suda `blockSlots` for controlled nested
36
+ content.
37
+ - Read platform-owned values such as white-label and ICP/public-security
38
+ records from runtime metadata helpers. Do not copy them into static props.
39
+ - Read public language links from `getSiteLocale(puck?.metadata)` and render the
40
+ provided label/href instead of constructing locale URLs.
41
+
42
+ Read `editor-compatibility.md` as well whenever layout positioning or client
43
+ behavior changes.
44
+
45
+ ## Design system
46
+
47
+ Build the entire theme from one editable design system. Sections, CMS views,
48
+ block slots, and starter pages must not invent independent palettes, type
49
+ scales, radii, shadows, or spacing.
50
+
51
+ - Define `sourceManifest.designSystem` in `src/manifest.ts` with `version`,
52
+ `defaultPresetId`, and complete named presets.
53
+ - Use stable engine token keys. At minimum define every color token the theme
54
+ consumes (`background`, `foreground`, `primary`, `primaryForeground`,
55
+ `secondary`, `secondaryForeground`, `accent`, `accentForeground`, `muted`,
56
+ `mutedForeground`) and consumed radius tokens (`card`, `button`, `input`).
57
+ - Do not invent platform-facing token names without checking generated types.
58
+ - Expose `designSystemField(...)` only at the layout root and initialize it with
59
+ `createThemeDesignDefault(sourceManifest.designSystem)`.
60
+ - Resolve tokens once in the root with
61
+ `resolveThemeDesignTokens(sourceManifest.designSystem, props.designSystem)`
62
+ and apply `createThemeDesignCssVariables(tokens)` there. Sections consume CSS
63
+ variables; they do not resolve presets independently.
64
+
65
+ For Tailwind, map Suda tokens through `@theme inline` and consume semantic
66
+ utilities such as `bg-primary`, `text-primary-foreground`, and `rounded-card`.
67
+ The `inline` keyword is required because Suda injects `--suda-*` variables on
68
+ the rendered theme root.
69
+
70
+ Hand-written CSS consumes `--suda-*` directly. Do not use Suda-backed aliases
71
+ such as `var(--color-primary)`, `var(--color-background)`, or
72
+ `var(--radius-card)` in hand-written rules. For derived values, declare a
73
+ theme-prefixed variable on the root:
74
+
75
+ ```css
76
+ .my-theme-root {
77
+ --my-theme-primary-hover: color-mix(in srgb, var(--suda-color-primary) 84%, black);
78
+ }
79
+
80
+ .my-theme-button:hover {
81
+ background: var(--my-theme-primary-hover);
82
+ }
83
+ ```
84
+
85
+ Build/check/publish reject hand-written consumption of Suda-backed Tailwind
86
+ aliases.
87
+
88
+ ## Visual consistency
89
+
90
+ - Centralize typography and spacing scales in `src/styles.css`.
91
+ - Reuse container, section padding, heading, eyebrow, body, card, button, input,
92
+ and media-frame patterns.
93
+ - Section props expose semantic decisions such as `tone`, `variant`, `columns`,
94
+ `mediaPosition`, `showImage`, or `spacing`, then map them to shared tokens and
95
+ classes.
96
+ - Do not expose raw color, typography, spacing, radius, or shadow fields merely
97
+ because CSS contains a value. Expose a design control only when editors should
98
+ intentionally customize it per instance.
99
+ - Use `color` and `spacing` fields only for real editor-facing controls; use
100
+ `select`/`radio` for named design-system variants.
101
+ - Local blocks inherit their host section's design system and should not define
102
+ independent palettes, type scales, radii, shadows, or spacing.
103
+ - CMS sections and ordinary post sections use the same containers, typography,
104
+ cards, media ratios, pagination, and states as the rest of the theme.
105
+ - Starter pages demonstrate one coherent design system. Vary content, order,
106
+ media, and semantic variants rather than hardcoding unrelated styles.
107
+ - Add a shared utility/token when multiple sections need it or the theme needs
108
+ a named pattern, not for one-off styling.
109
+
110
+ ## Styling and assets
111
+
112
+ - Tailwind v4 starts in `src/styles.css` with `@import "tailwindcss";` and
113
+ `@source "./**/*.{ts,tsx}";`.
114
+ - Scope theme CSS with generated theme-key classes. Avoid global resets that
115
+ affect the host editor or other themes.
116
+ - Theme-local assets live in top-level `assets/` and are copied to
117
+ `dist/assets/` during build.
118
+ - Use `themeAsset("assets/...")` for bundled assets in `defaultProps`, starter
119
+ pages, CMS templates, and AI examples.
120
+ - Do not import theme-local images from React code or hand-write
121
+ `themes/<themeKey>/<version>/...` paths. Full external CDN URLs are valid.
122
+ - Resolve user-selected media with `resolveAsset(puck?.metadata, value)` before
123
+ rendering.
124
+ - Required preview screenshots are `assets/preview/desktop.png`,
125
+ `assets/preview/tablet.png`, and `assets/preview/mobile.png`; generate them
126
+ with `suda theme capture`.
127
+ - Customize Vite, PostCSS, and Tailwind configuration only when necessary.
128
+ - Do not modify minified vendor CSS. Use precise scoped overrides unless an
129
+ explicitly maintained vendor patch is unavoidable.
130
+
131
+ ## Contact forms
132
+
133
+ When a theme offers a contact section, render the host-provided form from
134
+ `metadata.contactForm`. The platform owns field configuration, validation,
135
+ submissions, notifications, webhooks, permissions, and limits; the theme owns
136
+ public presentation only.
137
+
138
+ - Read the form through `getContactForm(puck?.metadata)` from
139
+ `@sudajs/theme-engine/runtime`.
140
+ - `suda theme dev` passes a default form through `metadata.contactForm` for
141
+ local preview.
142
+ - If no valid enabled form exists, render nothing for the form area. Do not
143
+ show a public unavailable/error fallback panel.
144
+ - Support and style `text`, `textarea`, `checkbox`, `radio`, and `select` fields.
145
+ - Render labels, keyboard focus, submit state, success state, validation errors,
146
+ and mobile layout accessibly.
147
+ - Use only placeholders supplied by form settings. Do not invent placeholders,
148
+ including an empty select option.
149
+ - Submit field values to `contactForm.endpoint`.
150
+ - Never hardcode notification channels, recipients, webhook URLs, ICP records,
151
+ or white-label branding in the section.
152
+ - Render hidden platform fields, including
153
+ `name={contactForm.honeypotField}`, as bare `<input type="hidden" />` nodes.
154
+ Do not wrap hidden inputs in labels, layout rows, grids, or visual field
155
+ components.
156
+
157
+ ## Verification
158
+
159
+ Before handoff:
160
+
161
+ ```bash
162
+ pnpm lint
163
+ pnpm typecheck
164
+ pnpm build
165
+ pnpm validate
166
+ suda theme check
167
+ ```
168
+
169
+ Run `suda theme capture` after visual changes and compare desktop, tablet, and
170
+ mobile output in editor preview and on the public site.
@@ -0,0 +1,231 @@
1
+ # Editor Compatibility
2
+
3
+ Read this guide before changing Header, Footer, PageOutlet, sticky/fixed UI,
4
+ transparent navigation, carousels, dropdowns, editor CSS, or client behavior.
5
+ Every public component must render consistently on the public site, in the Puck
6
+ Editor iframe, and in editor preview mode.
7
+
8
+ ## Stable roots and Puck mutation
9
+
10
+ Puck may attach selection/drag attributes and inline positioning directly to
11
+ the first real element returned by a component:
12
+
13
+ ```ts
14
+ el.setAttribute("data-puck-component", id);
15
+ el.setAttribute("data-puck-dnd", id);
16
+ el.style.position = "relative";
17
+ ```
18
+
19
+ - Never assume `[data-puck-component]` is an external wrapper. It may be the
20
+ component's own `<nav>`, `<header>`, `<section>`, or `<footer>`.
21
+ - Every public component should return one stable semantic real DOM root when
22
+ it controls spacing, background, sizing, stacking, positioning, or selection.
23
+ - Do not use a Fragment or `display: contents` for roots with those jobs.
24
+ - `PageOutlet` needs a real element when it owns overlap, negative margin,
25
+ positioning, or sizing.
26
+ - `:has(.component-root)` does not match an element that is itself the root.
27
+ Cover both shapes when either can occur:
28
+
29
+ ```css
30
+ .component-root[data-puck-component],
31
+ [data-puck-component]:has(.component-root) {
32
+ /* compatibility rule */
33
+ }
34
+ ```
35
+
36
+ Puck's inline `position: relative` beats ordinary CSS. If the actual root must
37
+ remain `sticky`, `fixed`, or `absolute`, use a precise editor-scoped rule with
38
+ `!important`. Never apply `position: relative !important` to every editor
39
+ component; it breaks navigation, overlays, and floating controls.
40
+
41
+ ## Editor scope and iframe height
42
+
43
+ Derive editor state from `puck?.metadata?.isEditor === true` and expose a stable
44
+ theme-root marker:
45
+
46
+ ```tsx
47
+ <div className="theme-root" data-suda-editor={isEditor ? "true" : undefined}>
48
+ {children}
49
+ </div>
50
+ ```
51
+
52
+ Keep editor-only fixes below `.theme-root[data-suda-editor="true"]`. Do not
53
+ globally override `.navbar`, `.section`, `[data-puck-component]`, or public-site
54
+ layout to repair the editor.
55
+
56
+ The editable page scrolls inside a same-origin iframe. The outer canvas/frame
57
+ may be only one viewport tall, so the iframe document must remain content-height
58
+ driven:
59
+
60
+ - Use `height: auto`, `min-height: 100vh`, and `overflow: visible` on the editor
61
+ theme root unless the theme requires a different proven arrangement.
62
+ - Ensure Puck's `#frame-root` and root `[data-puck-dropzone]` can grow with
63
+ content. Avoid `height: 100%` when it collapses the page to one viewport.
64
+ - Be careful with global `html`, `body`, `iframe`, `.hidden`, `img`, `button`,
65
+ `input`, and Tailwind preflight rules.
66
+ - Put third-party compatibility overrides in scoped `src/styles.css`; do not
67
+ change public behavior globally or patch minified vendor CSS.
68
+ - Scroll through every starter page in edit mode. All components must be
69
+ reachable without selecting them from the outline first.
70
+
71
+ ## Sticky and fixed positioning
72
+
73
+ Before implementing Header, announcement bars, floating CTAs, or any
74
+ sticky/fixed element, identify:
75
+
76
+ 1. The real positioned root and whether Puck mutates it.
77
+ 2. The actual scroll element in the public site and editor iframe.
78
+ 3. The intended sticky containing block.
79
+ 4. Every ancestor with `overflow`, `transform`, `filter`, `perspective`, or
80
+ `contain` that may change positioning.
81
+ 5. Whether the sticky parent is taller than the sticky element.
82
+ 6. Explicit `top` and `z-index` values.
83
+ 7. Whether iframe and outer-canvas coordinate systems differ.
84
+
85
+ For a layout Header, put sticky behavior on its real root. If that node receives
86
+ `data-puck-component`, correct Puck's inline position on that same node. Site and
87
+ editor may use different mechanics only when their visual result remains
88
+ equivalent.
89
+
90
+ Example covering both possible DOM shapes:
91
+
92
+ ```css
93
+ .theme-root[data-suda-editor="true"] .theme-navigation[data-puck-component],
94
+ .theme-root[data-suda-editor="true"]
95
+ [data-puck-component]:has(.theme-navigation)
96
+ .theme-navigation {
97
+ position: sticky !important;
98
+ top: 0;
99
+ z-index: 30;
100
+ }
101
+ ```
102
+
103
+ ## Transparent navigation
104
+
105
+ Transparent navigation is coordinated state and layout, not only a transparent
106
+ background. Handle all of these together:
107
+
108
+ - background and shadow;
109
+ - logo, menu text, and button colors;
110
+ - Hero/PageOutlet overlap beneath Header;
111
+ - transition to the solid state after a scroll threshold;
112
+ - Puck root-position mutation;
113
+ - identical SSR-first-frame and hydrated state.
114
+
115
+ Never render white logo/text above an accidentally white Header. Prefer React
116
+ Context for a page's transparency request, an SSR-readable DOM marker for the
117
+ first frame, and a real PageOutlet element for editor overlap. The public site
118
+ may use a spacer or equivalent layout strategy. Keep shared measurements in a
119
+ theme CSS variable such as `--theme-navigation-height`.
120
+
121
+ ## Scroll detection
122
+
123
+ Do not rely only on `window.scrollY` or a window scroll listener. The source may
124
+ be `document.scrollingElement`, the iframe document, or a nested
125
+ `overflow: auto/scroll` element, and scroll does not bubble.
126
+
127
+ - Find the nearest scrollable ancestor for each navigation instance and read
128
+ that element's `scrollTop`.
129
+ - Listen on the document with capture and passive mode:
130
+
131
+ ```ts
132
+ document.addEventListener("scroll", handler, { capture: true, passive: true });
133
+ ```
134
+
135
+ - Keep a window listener for public-site compatibility.
136
+ - Never assume site and editor use the same scroll element.
137
+
138
+ ## Client lifecycle
139
+
140
+ Runtime code must tolerate editor remounts, live prop updates, HMR, React Strict
141
+ Mode effect replay, frequent `MutationObserver` callbacks, and scripts loading
142
+ after `DOMContentLoaded`.
143
+
144
+ - Make initialization and event binding idempotent.
145
+ - Observe components inserted later and only relevant attribute changes.
146
+ - Do not use a permanent `window.__xxxBound` flag that blocks HMR reinitialization.
147
+ - Clean up effects, observers, timers, and listeners.
148
+ - Keep SSR markers and client state synchronized.
149
+ - Put optional browser setup in `clientHooks` from `src/client.ts`; never add
150
+ `src/runtime.client.ts(x)`.
151
+
152
+ ## Editor interactions
153
+
154
+ Edit mode may effectively disable descendant interaction:
155
+
156
+ ```css
157
+ [data-puck-component] * {
158
+ pointer-events: none;
159
+ user-select: none;
160
+ }
161
+
162
+ [data-puck-component] {
163
+ pointer-events: auto !important;
164
+ }
165
+ ```
166
+
167
+ An unclickable link, dropdown, carousel, or button in edit mode is not by itself
168
+ a theme bug. Never globally restore descendant pointer events because that
169
+ breaks selection and drag behavior. Test visitor interaction in editor preview
170
+ and on the public site.
171
+
172
+ ## Layout ownership
173
+
174
+ - Keep only Header, PageOutlet, Footer, and necessary global chrome such as an
175
+ announcement bar in `layoutConfig`.
176
+ - Render Header and Footer exactly once from the layout; do not repeat them in
177
+ starter pages or CMS templates.
178
+ - When moving chrome into the layout, remove duplicates from starter pages,
179
+ CMS templates, page categories, and AI examples.
180
+ - Give Header, PageOutlet, and Footer stable ids, with PageOutlet between Header
181
+ and Footer.
182
+ - Read white-label, ICP, and other footer metadata from runtime metadata; do not
183
+ persist duplicated platform values in component data.
184
+
185
+ ## Vendor CSS
186
+
187
+ Do not modify original third-party minified CSS unless the task explicitly
188
+ requires a maintained vendor patch. Prefer precise selectors in scoped
189
+ `src/styles.css`. If a patch is unavoidable, document the reproducible problem
190
+ and why an override cannot solve it.
191
+
192
+ ## Completion checklist
193
+
194
+ For a new theme, verify:
195
+
196
+ - stable real roots and an editor root marker;
197
+ - layout-owned Header, PageOutlet, and Footer with stable ids;
198
+ - Header sticky behavior in editor and site modes;
199
+ - transparent navigation first frame and scrolled state;
200
+ - correct scrollable-ancestor detection;
201
+ - idempotent runtime setup and cleanup;
202
+ - real-element PageOutlet overlap behavior;
203
+ - responsive behavior; and
204
+ - editor preview versus public-site parity.
205
+
206
+ Whenever Header, Footer, PageOutlet, transparent navigation, sticky/fixed UI,
207
+ carousel, dropdown/mobile menu, or a `100vh` Hero changes, test:
208
+
209
+ - public SSR first frame and post-hydration state;
210
+ - editor desktop, tablet, and mobile;
211
+ - edit and preview modes;
212
+ - top state and state after crossing scroll thresholds;
213
+ - HMR and live prop updates;
214
+ - exactly one Header and one Footer; and
215
+ - intact Puck selection and dragging.
216
+
217
+ ## Debugging order
218
+
219
+ When editor and site differ, inspect in this order before changing CSS:
220
+
221
+ 1. The component's actual root.
222
+ 2. The node receiving `data-puck-component`.
223
+ 3. Inline styles on that node.
224
+ 4. Computed `position`, `background`, `z-index`, and ancestor `overflow`.
225
+ 5. The actual scroll element and its `scrollTop`.
226
+ 6. Iframe versus outer-canvas coordinates.
227
+ 7. Whether client runtime initialized.
228
+ 8. Transparent-state context and SSR DOM markers.
229
+ 9. Stylesheet loading and cascade order.
230
+
231
+ Do not guess selectors repeatedly before confirming DOM and computed styles.