@wix/agent-skills 1.16.2 → 1.16.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 +1 -1
- package/skills/wix-app/references/EDITOR_REACT_COMPONENT.md +13 -6
- package/skills/wix-app/references/EMBEDDED_SCRIPT.md +6 -0
- package/skills/wix-app/references/editor-react-component/ANIMATED-COMPONENTS.md +3 -2
- package/skills/wix-app/references/editor-react-component/COMPONENT-API.md +32 -0
- package/skills/wix-app/references/editor-react-component/DESIGN-STATES.md +27 -4
- package/skills/wix-app/references/editor-react-component/MANIFEST-ERRORS.md +19 -0
- package/skills/wix-app/references/editor-react-component/PARTS.md +1 -2
- package/skills/wix-manage/SKILL.md +4 -2
- package/skills/wix-manage/references/bookings/create-class-service.md +1 -1
- package/skills/wix-manage/references/rich-content/author-ricos-rich-content.md +182 -5
package/package.json
CHANGED
|
@@ -67,10 +67,12 @@ File where you can override the generated manifest from `<componentName>.generat
|
|
|
67
67
|
3. Edit the generated React and CSS files in
|
|
68
68
|
`src/extensions/site/components/component-name/`. Apply
|
|
69
69
|
[`editor-react-component/DESIGN-STATES.md`](editor-react-component/DESIGN-STATES.md) while
|
|
70
|
-
authoring: every interactive named part must use interactive markup and
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
70
|
+
authoring: every interactive named part must use interactive markup, and
|
|
71
|
+
every native design state supported by the eligibility table must pair its
|
|
72
|
+
selector with the prefixed global modifier (for example,
|
|
73
|
+
`.cta:global(.component-name-cta--hover), .cta:hover`). A standalone
|
|
74
|
+
`:focus-visible` keyboard indicator on a non-input control is not an editor
|
|
75
|
+
design state and intentionally has no global modifier.
|
|
74
76
|
4. **MANDATORY** — execute the full A11y Review on the edited component per [`editor-react-component/A11Y-REVIEW.md`](editor-react-component/A11Y-REVIEW.md). Run both scanners over the component's `.tsx`/`.jsx` files, complete the Phase 3 manual semantic review, fix confirmed findings, and re-run the scanners after fixes. Reading the reference without executing these steps is not a review. Do not proceed to the build or report completion until the review is complete.
|
|
75
77
|
5. Run `npx wix build && npx wix generate manifest` so the editor picks up
|
|
76
78
|
the new/updated prop schema. This command regenerates manifest
|
|
@@ -79,7 +81,7 @@ File where you can override the generated manifest from `<componentName>.generat
|
|
|
79
81
|
≥ 1.1.210, but prop-triggered `ElementState` states need ≥ 1.1.215). If a
|
|
80
82
|
design state is missing from `<componentName>.generated.ts`, the installed
|
|
81
83
|
CLI is older than required — tell the user, and let them decide whether to
|
|
82
|
-
upgrade.
|
|
84
|
+
upgrade. If the command exits with an error, diagnose using [`editor-react-component/MANIFEST-ERRORS.md`](editor-react-component/MANIFEST-ERRORS.md).
|
|
83
85
|
6. Update `Component.extensions.ts` file according to [`editor-react-component/COMPONENT-CONFIGURATION.md`](editor-react-component/COMPONENT-CONFIGURATION.md)
|
|
84
86
|
|
|
85
87
|
If the component's primary content is a **playable animation** (Lottie/JSON,
|
|
@@ -102,6 +104,11 @@ before writing the code. Triggers, and the only cases that warrant it:
|
|
|
102
104
|
|
|
103
105
|
Otherwise, do not reach for a hook at all — data the site owner can type or pick
|
|
104
106
|
belongs in props.
|
|
107
|
+
If the component displays **an array of items where only one body is visible at a
|
|
108
|
+
time** (tabs, slides, steps), follow the
|
|
109
|
+
[active-item rules](editor-react-component/COMPONENT-API.md#active-item-components-one-body-visible-at-a-time)
|
|
110
|
+
in [`editor-react-component/COMPONENT-API.md`](editor-react-component/COMPONENT-API.md) (`ActiveItemIndex<>`,
|
|
111
|
+
`name` on each item, render-all-panels visibility).
|
|
105
112
|
|
|
106
113
|
Reference: when modifying an _existing_ component, follow
|
|
107
114
|
[`editor-react-component/EDIT-FLOW.md`](editor-react-component/EDIT-FLOW.md).
|
|
@@ -117,7 +124,7 @@ Topic-focused references (rules + patterns + common mistakes in one place):
|
|
|
117
124
|
- [`editor-react-component/DESIGN-STATES.md`](editor-react-component/DESIGN-STATES.md) — Which design states a part supports (heuristic) and how to author them
|
|
118
125
|
- [`editor-react-component/DIRECTIONALITY.md`](editor-react-component/DIRECTIONALITY.md) — RTL/LTR rules and patterns
|
|
119
126
|
- [`editor-react-component/PROPS-VS-CSS.md`](editor-react-component/PROPS-VS-CSS.md) — What should be a React prop vs CSS
|
|
120
|
-
- [`editor-react-component/COMPONENT-API.md`](editor-react-component/COMPONENT-API.md) — Props structure, elementProps, data types, file splitting, containers, array props
|
|
127
|
+
- [`editor-react-component/COMPONENT-API.md`](editor-react-component/COMPONENT-API.md) — Props structure, elementProps, data types, file splitting, containers, array props, active-item components
|
|
121
128
|
- [`editor-react-component/FUNCTION-HANDLERS.md`](editor-react-component/FUNCTION-HANDLERS.md) — Standard SDK event handler props, DOM wiring, custom callbacks
|
|
122
129
|
- [`editor-react-component/ANIMATED-COMPONENTS.md`](editor-react-component/ANIMATED-COMPONENTS.md) — Play/pause control and autoplay for animated/playable components
|
|
123
130
|
- [`editor-react-component/COMPONENT-PREVIEW.md`](editor-react-component/COMPONENT-PREVIEW.md) — Editor-specific entry point (`component.preview.tsx`), `useIsEditMode()`, when to modify
|
|
@@ -230,6 +230,12 @@ await embeddedScripts.embedScript({
|
|
|
230
230
|
});
|
|
231
231
|
```
|
|
232
232
|
|
|
233
|
+
## Releasing Updates
|
|
234
|
+
|
|
235
|
+
`embedScript()` is not only a one-time setup step. A site only ever renders whatever content was active the last time `embedScript()` was called for it — releasing a new app version (even a minor one, e.g. via `wix release`, that only changes the script's HTML/JS with no dynamic-parameter changes) does **not** re-embed the script on sites that already called it. Always prompt the site owner to re-run the dashboard-page action (or call `embedScript()` yourself) after any release that changes an embedded script's content, or production sites will silently keep serving the old version.
|
|
236
|
+
|
|
237
|
+
Adding, removing, or renaming a dynamic parameter is additionally a **major** version change — those sites only pick up the new version after the user clicks Update on the Manage Apps page (or you drive them through the app-installer update flow). See [About App Versioning](https://dev.wix.com/docs/build-apps/manage-your-app/versioning/about-app-versioning).
|
|
238
|
+
|
|
233
239
|
## Enable Embedded Script Permission
|
|
234
240
|
|
|
235
241
|
After implementation, the app developer must manually enable the embedded script permission:
|
|
@@ -32,7 +32,7 @@ implementation detail.
|
|
|
32
32
|
|
|
33
33
|
1. **Props** — `autoPlay`, `loop`, `pauseButtonVisibility`
|
|
34
34
|
2. **Playback state** — `isPlaying` + `handlePause` / `handleResume`
|
|
35
|
-
3. **Play/pause button** — overlay `<button>` with inline SVG icon + CSS positioning and optional hover-only visibility; **must be a named part** (`classNames('component-name-play-button', styles.playButton)`) with hover
|
|
35
|
+
3. **Play/pause button** — overlay `<button>` with inline SVG icon + CSS positioning and optional hover-only visibility; **must be a named part** (`classNames('component-name-play-button', styles.playButton)`) with a hover design state and a standalone `:focus-visible` keyboard indicator
|
|
36
36
|
4. **Modify `component.preview.tsx`** — suppress autoplay in editor design mode
|
|
37
37
|
5. **Respect `prefers-reduced-motion`** — suppress autoplay when the OS requests reduced motion
|
|
38
38
|
|
|
@@ -126,7 +126,8 @@ Position the button absolutely so it overlays the content without pushing other
|
|
|
126
126
|
/* e.g. background: rgba(255, 255, 255, 1); */
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
-
|
|
129
|
+
/* Keyboard indicator only — deliberately unpaired: focus is not an editor
|
|
130
|
+
design state on a non-input control. Do not add a --focus modifier here. */
|
|
130
131
|
.playButton:focus-visible {
|
|
131
132
|
outline: 2px solid currentColor;
|
|
132
133
|
outline-offset: 2px;
|
|
@@ -130,6 +130,38 @@ when arithmetic is needed: `price: number`, not `price: string`).
|
|
|
130
130
|
|
|
131
131
|
When the parent component defines an array prop (e.g., `items`), child/item components receive a single item directly. They do NOT redeclare the data structure in their own props.
|
|
132
132
|
|
|
133
|
+
### Active-item components (one body visible at a time)
|
|
134
|
+
|
|
135
|
+
Apply when the UI shows **one array item body at a time** (tabs, slideshow,
|
|
136
|
+
steps). Skip for always-visible lists, multi-select, or multi-expand accordion.
|
|
137
|
+
|
|
138
|
+
- Each item needs **`name: string`** — Studio hat labels (not `label` or other keys).
|
|
139
|
+
- Pair the array with **`ActiveItemIndex<'arrayPropName'>`** from
|
|
140
|
+
`@wix/react-component-utils`; the type param must match the array prop name.
|
|
141
|
+
Default the index to `0` in `defaultProps`.
|
|
142
|
+
- Render **all** item bodies (`.map`); toggle visibility with `--active` +
|
|
143
|
+
`display: none` on inactive panels (functional visibility — see
|
|
144
|
+
[`PROPS-VS-CSS.md`](PROPS-VS-CSS.md)), plus `aria-hidden` and `inert` on
|
|
145
|
+
inactive bodies.
|
|
146
|
+
- Keyboard navigation between triggers; ARIA roles per
|
|
147
|
+
[`ACCESSIBILITY.md`](ACCESSIBILITY.md).
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
import type { ActiveItemIndex } from '@wix/react-component-utils';
|
|
151
|
+
|
|
152
|
+
export type ItemType = { name: string; body: string };
|
|
153
|
+
|
|
154
|
+
export type MyComponentProps = {
|
|
155
|
+
items: Array<ItemType>;
|
|
156
|
+
activeItem: ActiveItemIndex<'items'>;
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
export const defaultProps = {
|
|
160
|
+
items: [{ name: 'Item 1', body: '…' }],
|
|
161
|
+
activeItem: 0,
|
|
162
|
+
} satisfies Omit<MyComponentProps, 'id' | 'className'>;
|
|
163
|
+
```
|
|
164
|
+
|
|
133
165
|
### Array Element Types: Always Objects with Named Keys
|
|
134
166
|
|
|
135
167
|
Array elements MUST be objects with named keys. This enables stable item identity (each item can carry its own `id`/`key`), non-breaking extension (new fields can be added later without changing the prop signature), and semantic naming (each value has meaning instead of being an opaque scalar).
|
|
@@ -9,14 +9,31 @@ and `.module.css`.
|
|
|
9
9
|
|
|
10
10
|
| Element | Author these states |
|
|
11
11
|
|---|---|
|
|
12
|
-
| Interactive — `button`/`a`/`input`/`select`/`textarea`/`summary`, an interactive `role
|
|
12
|
+
| Interactive — `button`/`a`/`input`/`select`/`textarea`/`summary`, an interactive `role`, or an interactive handler (`onClick`/`onMouseEnter`/`onFocus`/…) | `hover` |
|
|
13
|
+
| Input field — `select`/`textarea`; `input` except `hidden`/`button`/`submit`/`reset`/`image`; or an input-widget `role` (`checkbox`/`radio`/`switch`/`slider`/`spinbutton`/`textbox`/`searchbox`/`combobox`/`listbox`) | `focus` |
|
|
14
|
+
| Other interactive element whose editable focus appearance the user explicitly requests | `focus` |
|
|
13
15
|
| Disableable — `button`/`input`/`select`/`textarea`/`fieldset` or a disableable role | `disabled` (+ `invalid` for `input`/`select`/`textarea`) |
|
|
14
16
|
| Has a selectable/variant value in its data — `selected`/`active`/`current`/`open`/`expanded`/`checked`/`featured` | that custom state |
|
|
15
17
|
| None of the above | no states — resting style only |
|
|
16
18
|
|
|
19
|
+
An explicit `role` overrides the tag's implicit semantics: `<input role="button">`
|
|
20
|
+
is a non-input control, and `<div role="checkbox">` is an input field.
|
|
21
|
+
|
|
22
|
+
A `focus` design state is an editor styling control, not the keyboard focus
|
|
23
|
+
indicator itself. Do not add a global `--focus` modifier to a non-input control
|
|
24
|
+
unless the user explicitly asks for an editable focus state. A control that
|
|
25
|
+
restyles its own `outline`, `background`, or `border` MUST still carry a
|
|
26
|
+
standalone `:focus-visible` rule — that rule is its only keyboard affordance.
|
|
27
|
+
|
|
17
28
|
A custom state may also be driven by a root-level boolean prop (e.g.
|
|
18
29
|
`isFeatured`) instead of by markup or item data — see §5.
|
|
19
30
|
|
|
31
|
+
For **active-item components** (tabs, slideshow, steps), the `--active` state on
|
|
32
|
+
each item is driven by comparing the item's index to the active-index prop
|
|
33
|
+
(`index === activeItem`), not by a per-item boolean flag — see
|
|
34
|
+
[Active-item components](COMPONENT-API.md#active-item-components-one-body-visible-at-a-time)
|
|
35
|
+
in [`COMPONENT-API.md`](COMPONENT-API.md).
|
|
36
|
+
|
|
20
37
|
## 2. Name the state class
|
|
21
38
|
|
|
22
39
|
Flat: the element's own global class + `--<state>`. Because inner-part
|
|
@@ -31,9 +48,12 @@ nested (`card__row--selected`).
|
|
|
31
48
|
Put the resting value in the bare class; put only the state override in the
|
|
32
49
|
state selector.
|
|
33
50
|
|
|
34
|
-
- **Native** — pair the pseudo-class with the `:global` modifier.
|
|
35
|
-
state needs both selectors in the same rule; do not
|
|
36
|
-
states (for example, pseudo-only `hover` plus
|
|
51
|
+
- **Native design state** — pair the pseudo-class with the `:global` modifier.
|
|
52
|
+
Every exposed native state needs both selectors in the same rule; do not
|
|
53
|
+
split the pair across states (for example, pseudo-only `hover` plus
|
|
54
|
+
global-only `disabled`). A standalone `:focus-visible` accessibility rule on
|
|
55
|
+
a non-input control is intentionally not an editor design state and therefore
|
|
56
|
+
has no `:global` modifier.
|
|
37
57
|
- **Custom** — the `:global` modifier alone.
|
|
38
58
|
|
|
39
59
|
The bare selector is the short **module** class (`.cta`); the `:global(...)`
|
|
@@ -51,6 +71,9 @@ state class is the **prefixed** global one.
|
|
|
51
71
|
.cta:disabled {
|
|
52
72
|
opacity: 0.5;
|
|
53
73
|
}
|
|
74
|
+
.cta:focus-visible {
|
|
75
|
+
outline: 2px solid currentColor;
|
|
76
|
+
} /* keyboard indicator only; not an editor design state */
|
|
54
77
|
.plan-row:global(.pricing-card-plan-row--selected) {
|
|
55
78
|
border-color: #6366f1;
|
|
56
79
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Manifest Build Errors
|
|
2
|
+
|
|
3
|
+
The error name is always the first token before the colon (`IoError:`, `ParseError:`, `NotFoundError:`). Find the matching row, apply the fix, then re-run both commands.
|
|
4
|
+
|
|
5
|
+
| Error | Fix |
|
|
6
|
+
|---|---|
|
|
7
|
+
| `IoError: Failed to load compiled bundle` | Re-run `npx wix build`; fix any TypeScript/build errors it reports first |
|
|
8
|
+
| `IoError: Failed to access component "X" from compiled bundle` | Corrupted artifact — delete `dist/`, re-run `npx wix build` from scratch |
|
|
9
|
+
| `IoError: CSS selector matching failed for "X"` | Fix invalid selector syntax in `<componentName>.module.css` |
|
|
10
|
+
| `IoError: Component "X" render produced no root element` | Component returns null or bare text — ensure it always renders a root JSX element |
|
|
11
|
+
| `IoError` (render phase, no other pattern) | SSR crash — remove browser-only APIs (`window`, `document`, `localStorage`) used at render time; ensure props have defaults |
|
|
12
|
+
| `NotFoundError: File not found` | Source `.tsx` missing — restore it or re-scaffold with `npx wix generate` |
|
|
13
|
+
| `NotFoundError: Component "X" was not found as a named export` | Confirm `component.tsx` has a default export and re-run `npx wix build` |
|
|
14
|
+
| `ParseError: Failed to parse TypeScript config` | Fix `tsconfig.json`; run `npx tsc --noEmit` to surface the error |
|
|
15
|
+
| `ParseError: Failed to extract component types from "X"` | TypeScript error in the component — run `npx tsc --noEmit` and fix |
|
|
16
|
+
| `ParseError: Failed to compile "<file.scss>"` | SCSS syntax error — line/column is in the error message |
|
|
17
|
+
| `ParseError: Failed to parse CSS file for component "X"` | CSS syntax error in `.module.css` — line/column is in the error message |
|
|
18
|
+
|
|
19
|
+
After each fix, re-run `npx wix build && npx wix generate manifest` to confirm.
|
|
@@ -23,7 +23,7 @@ An element deserves its own named part if a site owner would plausibly need to i
|
|
|
23
23
|
- ❌ **State or variant** (`active`, `selected`, `current`, `disabled`, `open`, `checked`) — not a part, it's a **design state** of a part. Implement it per [`DESIGN-STATES.md`](DESIGN-STATES.md): native states from interactive markup; custom states as a flat global modifier on the element's own (component-name-prefixed) class (e.g. `carousel-slide--active`, toggled in JSX) plus a `:global(.carousel-slide--active)` CSS rule. The modifier is a state, not a named part.
|
|
24
24
|
- ❌ **Hidden/shown state of an existing part** — the hidden and shown states of a part are not separate parts. The element itself should be a named part (so the editor can control its visibility per breakpoint); creating an additional part to represent its hidden or shown variant is wrong.
|
|
25
25
|
- ❌ **Pure grouping wrapper** — not a part. A `<div>` whose only role is to hold already-named siblings has no independent editor surface; let layout live in the parent's CSS.
|
|
26
|
-
- ❌ **Child with no independent editor surface** — not a part if its styling and data are already fully owned by the parent. This applies regardless of how many siblings it has. Two ways a child's editor surface can already be covered by the parent: (1) its CSS properties are inherited or set via the parent's rule; (2) its data is defined on the parent. **Canonical example:** an `<img>` inside a carousel slide whose `src` and `alt` come from the parent slide's data, and whose visual properties (object-fit, border-radius) could equally be set on the slide's own CSS rule — the slide already owns both data and styling, so the `<img>` is not a named part. Use a CSS module class for any structural CSS it needs. **Exception — interactive HTML elements (`<button>`, `<a>`, `<input>`) are never excluded by this criterion.** They always carry an independent styling surface: background, border, border-radius,
|
|
26
|
+
- ❌ **Child with no independent editor surface** — not a part if its styling and data are already fully owned by the parent. This applies regardless of how many siblings it has. Two ways a child's editor surface can already be covered by the parent: (1) its CSS properties are inherited or set via the parent's rule; (2) its data is defined on the parent. **Canonical example:** an `<img>` inside a carousel slide whose `src` and `alt` come from the parent slide's data, and whose visual properties (object-fit, border-radius) could equally be set on the slide's own CSS rule — the slide already owns both data and styling, so the `<img>` is not a named part. Use a CSS module class for any structural CSS it needs. **Exception — interactive HTML elements (`<button>`, `<a>`, `<input>`) are never excluded by this criterion.** They always carry an independent styling surface: background, border, border-radius, interaction states, color, and typography are all meaningfully tunable per element in the editor. Apply this exclusion only to purely static structural elements (layout wrappers, grouping containers, decorative dividers). This makes an interactive element a named part **when it is an inner element** — if it is the elected root (Step 0), it is already named by `'<component-name>'` and must not be given a prefixed inner-part class as well.
|
|
27
27
|
- ❌ **Positional duplicate** — not separate parts. Elements that are semantically identical and differ only in position (e.g. prev/next buttons) are one part; differentiate position with CSS (`:first-of-type` / `:last-of-type`, `data-` attribute, or `:nth-child`).
|
|
28
28
|
- ✅ **Passes all checks** — a named part. Classify as **Semantic** (needs `elementProps`: data, behavior, direction, event handlers) or **Styling-only** (CSS class is sufficient).
|
|
29
29
|
|
|
@@ -36,4 +36,3 @@ Before finalising, verify each proposed part against its parent:
|
|
|
36
36
|
If yes — the part adds no independent editor surface and should be removed. This catches rationalisation after-the-fact ("the parent *could* expose this CSS property too, but so could the child separately"). When in doubt, fewer parts is usually better.
|
|
37
37
|
|
|
38
38
|
Then run the same question in reverse, on the root: if the root has exactly one child part and no editor surface of its own, it is a plumbing wrapper — delete it and promote the child per Step 0.
|
|
39
|
-
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: wix-manage
|
|
3
|
-
description: "Wix business solution management recipes — REST API operations for configuring and managing Wix business solutions. Routes to: stores, bookings, get-paid, CMS, contacts, forms, media, app-installation, pricing-plans, restaurants, rich-content, sites, blog, calendar, domains, events, site-properties, ecommerce, marketing, google-ads, analytics, accessibility, dashboard-navigation."
|
|
3
|
+
description: "Wix business solution management recipes — REST API operations for configuring and managing Wix business solutions. Routes to: stores, bookings, get-paid, CMS, contacts, forms, media, app-installation, pricing-plans, restaurants, ricos rich-content, sites, blog, calendar, domains, events, site-properties, ecommerce, marketing, google-ads, analytics, accessibility, dashboard-navigation."
|
|
4
4
|
compatibility: Requires Wix REST API access (API key or OAuth).
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -341,11 +341,13 @@ These recipes do NOT cover frontend development or SDK usage for displaying data
|
|
|
341
341
|
|
|
342
342
|
## Rich Content
|
|
343
343
|
|
|
344
|
+
> **Routing rule (READ FIRST).** For every request to hand-author, output, or return Ricos / `richContent` JSON (`nodes` tree) for Blog, Stores, Events, or CMS, use the available full-documentation reading capability to retrieve and read the canonical [Author Ricos Rich Content](references/rich-content/author-ricos-rich-content.md) recipe before using API schema search, convert/validate APIs, or memory. This also applies when the user asks for JSON only.
|
|
345
|
+
|
|
344
346
|
### [Ricos Converter Service](references/rich-content/ricos-converter-service.md)
|
|
345
347
|
**Technical:** Validates and converts content between Ricos documents and HTML/Markdown/plain text using the Ricos Documents API. Covers plugin configuration, format conversion in both directions, and document validation.
|
|
346
348
|
|
|
347
349
|
### [Author Ricos Rich Content](references/rich-content/author-ricos-rich-content.md)
|
|
348
|
-
**Technical:**
|
|
350
|
+
**Technical:** Required first source for every request to hand-author, output, or return Ricos / `richContent` JSON (`nodes` tree) for Blog, Stores, Events, or CMS. Retrieve and read this full article before API schema search or constructing the JSON. Covers node shapes — paragraphs, headings, lists, blockquotes, dividers, tables with cell fills, code blocks, images, buttons, audio, video, galleries, collapsible lists, HTML embeds — plus inline text decorations (including spoiler) and nesting rules.
|
|
349
351
|
|
|
350
352
|
---
|
|
351
353
|
|
|
@@ -149,7 +149,7 @@ curl -X POST 'https://www.wixapis.com/bookings/v2/bulk/services/create' \
|
|
|
149
149
|
- `defaultCapacity` is **required** — sets max participants per session
|
|
150
150
|
- After creation, class sessions must be scheduled separately via `bulkCreateEvents` using the returned `service.schedule.id` (see [Create and Update Booking Services](./create-and-update-booking-services.md))
|
|
151
151
|
|
|
152
|
-
Save the
|
|
152
|
+
**Response:** Save the service ID from `results[0].itemMetadata.id`
|
|
153
153
|
|
|
154
154
|
---
|
|
155
155
|
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: "Author Ricos Rich Content"
|
|
3
|
-
description:
|
|
3
|
+
description: Authoritative recipe for hand-authoring valid Ricos rich-content JSON (the richContent/nodes tree) used across Wix Blog posts, Stores product descriptions, Events, and CMS rich-text fields. Use whenever a user asks to create, output, or return Ricos, richContent, or nodes-tree JSON; retrieve and read this full recipe before API schema search or constructing the JSON. Covers paragraphs, headings, lists, blockquotes, dividers, tables, code blocks, images, buttons, audio, video, galleries, collapsible lists, HTML embeds, inline decorations, and nesting rules.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Author Ricos Rich Content
|
|
7
7
|
|
|
8
|
+
> **Routing rule (READ FIRST).** When hand-authoring or returning Ricos / `richContent` JSON, use **this recipe** — the shapes, nesting rules, spacer paragraphs, and plugin nodes below. Do not rely on API schema search alone; it misses those details. After this recipe loads, do not perform additional schema or documentation searches for node types covered here; compose and self-audit the JSON from this file. When the user only wants JSON output, **do not** call Ricos convert/validate APIs.
|
|
9
|
+
|
|
8
10
|
Ricos is Wix's rich-content format — a tree of typed nodes serialized as JSON. The same structure is embedded by many products: a Blog post's `draftPost.richContent`, a Store product's rich description, an Events description, and CMS rich-text fields all expect a Ricos document. This recipe is the **authoring reference for that node tree**: the valid shape of each node, how nodes nest, and how to format text. It is intentionally product-agnostic — the consuming API decides *where* the document goes; this recipe governs *what a valid document looks like*.
|
|
9
11
|
|
|
10
12
|
> A Ricos document is an object with a `nodes` array: `{ "nodes": [ /* block nodes */ ] }`. Whatever field the consuming API exposes (e.g. `richContent`), it holds this object. For validating or converting an existing document to/from HTML/Markdown, see [Ricos Converter Service](ricos-converter-service.md).
|
|
@@ -15,10 +17,58 @@ Ricos is Wix's rich-content format — a tree of typed nodes serialized as JSON.
|
|
|
15
17
|
- Every node carries a `type`, an optional `id`, and (for container nodes) a `nodes` array of children. Node `id`s are optional when authoring for a create request — the API generates them; the examples below omit `id` for brevity.
|
|
16
18
|
- **TEXT is a leaf.** A TEXT node only ever lives inside a `PARAGRAPH`, `HEADING`, or `CODE_BLOCK`. It must **never** sit directly in the root `nodes` array or inside a `LIST_ITEM`, `BLOCKQUOTE`, or `TABLE_CELL` — those must contain a `PARAGRAPH` (or `HEADING`) that then contains the TEXT. See [Nesting rules](#nesting-rules).
|
|
17
19
|
- Failing to wrap TEXT correctly produces the parse error **"Expected a paragraph node but found TEXT"**.
|
|
20
|
+
- **Block nodes do not get automatic vertical gap when rendered** — insert empty `PARAGRAPH` spacer nodes between sibling blocks when you need breathing room. See [Vertical spacing between blocks](#vertical-spacing-between-blocks).
|
|
21
|
+
|
|
22
|
+
## Required plugin shape checklist
|
|
23
|
+
|
|
24
|
+
Use this checklist while composing and again before returning JSON. Treat every path below as required for the described request:
|
|
25
|
+
|
|
26
|
+
- **Button CTA:** emit a root-level `BUTTON`, never a `PARAGRAPH` with an inline `LINK`. Include `buttonData.type: "LINK"`, `buttonData.text`, `buttonData.link.url`, `buttonData.link.target`, and `buttonData.containerData`.
|
|
27
|
+
- **Hosted audio:** emit `{ "type": "AUDIO", "nodes": [], "audioData": { ... } }`. The empty `nodes` array is on the AUDIO node beside `audioData` — never inside `audioData`. Include `audioData.containerData.alignment`, `width`, and `textWrap`, plus `audioData.audio.src.id`.
|
|
28
|
+
- **Video with thumbnail:** place `videoData.video.src` and `videoData.video.duration` inside `video`; place `videoData.thumbnail` beside `video`, with `src.id`, `width`, and `height`.
|
|
29
|
+
- **Gallery:** emit one root-level `GALLERY`. Put images in `galleryData.items`, not as child IMAGE nodes. Every item uses `image.media` with `src`, `width`, and `height`; include `galleryData.options.layout.type` and `numberOfColumns`.
|
|
30
|
+
- **Collapsible FAQ:** use `COLLAPSIBLE_LIST → COLLAPSIBLE_ITEM → COLLAPSIBLE_ITEM_TITLE | COLLAPSIBLE_ITEM_BODY → PARAGRAPH → TEXT`. Every PARAGRAPH includes `paragraphData`, and every item includes both title and body.
|
|
31
|
+
- **HTML embed:** emit a root-level `HTML` with `htmlData.source`, either `url` or `html`, and `htmlData.containerData.width`, `height`, and `alignment`.
|
|
32
|
+
|
|
33
|
+
## Vertical spacing between blocks
|
|
34
|
+
|
|
35
|
+
The renderer does **not** add margin between stacked block nodes. Adjacent paragraphs, headings, images, videos, tables, collapsible lists, buttons, galleries, and other blocks will sit flush against each other unless you separate them explicitly.
|
|
36
|
+
|
|
37
|
+
**Use an empty `PARAGRAPH` as a spacer** — a paragraph with no `nodes` (no TEXT children):
|
|
38
|
+
|
|
39
|
+
```json
|
|
40
|
+
{ "type": "PARAGRAPH" }
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Place spacer paragraphs at the root `nodes` level (or inside a `CARD` page) wherever layout needs vertical rhythm:
|
|
44
|
+
|
|
45
|
+
- **After headings** — before the following body copy or media block.
|
|
46
|
+
- **Between paragraphs** — when two text blocks should not run together visually.
|
|
47
|
+
- **After block plugins** — images, videos, audio, galleries, tables, collapsible lists, HTML embeds, buttons, dividers, and lists often need a spacer before the **next distinct section** (not between every adjacent block in a minimal snippet the user enumerated).
|
|
48
|
+
- **After blockquotes** — only when the following block is a separate section; skip the spacer when the user asked for a blockquote immediately followed by a specific paragraph in one compact output.
|
|
49
|
+
- **Before major sections** — optional extra spacer when transitioning from one content group to another.
|
|
50
|
+
|
|
51
|
+
When the user requests **only** specific nodes in one JSON output (e.g. "a blockquote and then a paragraph"), include exactly those nodes — do not add spacer paragraphs they did not ask for.
|
|
52
|
+
|
|
53
|
+
Do **not** rely on `\n` inside `textData.text` for spacing — that does not create a real line break between blocks. Do **not** expect `paragraphData` margins or `containerData` alone to separate unrelated sibling nodes; the empty paragraph is the supported spacing mechanism.
|
|
54
|
+
|
|
55
|
+
**Example — spacer after an image and before a table:**
|
|
56
|
+
|
|
57
|
+
```json
|
|
58
|
+
{
|
|
59
|
+
"nodes": [
|
|
60
|
+
{ "type": "IMAGE", "imageData": { /* … */ } },
|
|
61
|
+
{ "type": "PARAGRAPH" },
|
|
62
|
+
{ "type": "TABLE", "nodes": [ /* … */ ], "tableData": { /* … */ } },
|
|
63
|
+
{ "type": "PARAGRAPH" },
|
|
64
|
+
{ "type": "PARAGRAPH", "nodes": [ { "type": "TEXT", "textData": { "text": "Next section.", "decorations": [] } } ], "paragraphData": {} }
|
|
65
|
+
]
|
|
66
|
+
}
|
|
67
|
+
```
|
|
18
68
|
|
|
19
69
|
## Block node shapes
|
|
20
70
|
|
|
21
|
-
**PARAGRAPH** — the base text container. An empty paragraph — `{ "type": "PARAGRAPH" }` —
|
|
71
|
+
**PARAGRAPH** — the base text container. An empty paragraph — `{ "type": "PARAGRAPH" }` — is the **vertical spacer** between block nodes (the renderer adds no gap on its own). See [Vertical spacing between blocks](#vertical-spacing-between-blocks). `paragraphData.textStyle.textAlignment` accepts `AUTO`·`LEFT`·`CENTER`·`RIGHT`·`JUSTIFY`:
|
|
22
72
|
|
|
23
73
|
```json
|
|
24
74
|
{
|
|
@@ -142,6 +192,122 @@ Ricos is Wix's rich-content format — a tree of typed nodes serialized as JSON.
|
|
|
142
192
|
}
|
|
143
193
|
```
|
|
144
194
|
|
|
195
|
+
**BUTTON** — a standalone call-to-action block. **Not the same as an inline `LINK` decoration** on TEXT (that is hyperlinked body copy; this is a labeled button control). **`buttonData.type` is required** on every BUTTON node — use `"LINK"` for URL navigation or `"ACTION"` for viewer click handlers (never omit `type` even when `link` is present). Two `buttonData.type` values:
|
|
196
|
+
|
|
197
|
+
- **`LINK`** — navigates to a URL when clicked (`link.url`, `link.target`: `BLANK`·`SELF`, optional `link.rel`).
|
|
198
|
+
- **`ACTION`** — triggers a viewer `onClick` handler (store the node in JSON; behavior is configured in the Ricos viewer, not in the document body).
|
|
199
|
+
|
|
200
|
+
```json
|
|
201
|
+
{
|
|
202
|
+
"type": "BUTTON",
|
|
203
|
+
"buttonData": {
|
|
204
|
+
"type": "LINK",
|
|
205
|
+
"text": "Get Started",
|
|
206
|
+
"link": { "url": "https://example.com", "target": "BLANK", "rel": { "nofollow": true } },
|
|
207
|
+
"containerData": { "alignment": "CENTER", "width": { "size": "ORIGINAL" }, "textWrap": true },
|
|
208
|
+
"styles": { "borderRadius": 4, "borderWidth": 0, "backgroundColor": "#116DFF", "textColor": "#FFFFFF" }
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
**AUDIO** — uploaded audio via Wix Media `src.id`, or an embed variant whose `audioData.html` holds an iframe (SoundCloud, Spotify). Include `audioData.containerData` with `alignment` and width settings, and use an empty `nodes: []` array because AUDIO has no child nodes. Optional fields include `coverImage` (`src.id`, `width`, `height`), `name`, `authorName`, and `disableDownload`:
|
|
214
|
+
|
|
215
|
+
```json
|
|
216
|
+
{
|
|
217
|
+
"type": "AUDIO",
|
|
218
|
+
"nodes": [],
|
|
219
|
+
"audioData": {
|
|
220
|
+
"containerData": { "alignment": "CENTER", "width": { "size": "CONTENT" }, "textWrap": true },
|
|
221
|
+
"audio": { "src": { "id": "mp3/f0f74f_48772df0375c41cd88e8e29370ccf899" } },
|
|
222
|
+
"disableDownload": true,
|
|
223
|
+
"coverImage": { "src": { "id": "f0f74f_2973832f552e4002b58ec6abbe7fce71~mv2.png" }, "width": 436, "height": 524 },
|
|
224
|
+
"name": "Track title",
|
|
225
|
+
"authorName": "Artist name"
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
**VIDEO** — Wix Media `video.src.id`, `duration` (seconds), and a `thumbnail` (`src.id`, `width`, `height`). YouTube and other embeds use the same `VIDEO` node shape with the appropriate media id:
|
|
231
|
+
|
|
232
|
+
```json
|
|
233
|
+
{
|
|
234
|
+
"type": "VIDEO",
|
|
235
|
+
"videoData": {
|
|
236
|
+
"containerData": { "width": { "size": "CONTENT" }, "alignment": "CENTER" },
|
|
237
|
+
"video": { "src": { "id": "video/11062b_a552731f40854d16a91627687fb8d1a6/1080p/mp4/file.mp4" }, "duration": 14.08 },
|
|
238
|
+
"thumbnail": { "src": { "id": "media/11062b_a552731f40854d16a91627687fb8d1a6f000.jpg" }, "width": 1920, "height": 1080 }
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
**GALLERY** — multiple images in one block. Each `items[]` entry wraps `image.media` with `src` (`id` or `url`), `width`, and `height`. `galleryData.options` controls layout (`layout.type`: `GRID`, `numberOfColumns`, `orientation`; `item.ratio`, `item.crop`; `thumbnails.placement`):
|
|
244
|
+
|
|
245
|
+
```json
|
|
246
|
+
{
|
|
247
|
+
"type": "GALLERY",
|
|
248
|
+
"galleryData": {
|
|
249
|
+
"containerData": { "width": { "size": "CONTENT" }, "alignment": "CENTER", "textWrap": true },
|
|
250
|
+
"items": [
|
|
251
|
+
{ "image": { "media": { "src": { "id": "8bb438_36726a2d14ec44ee9edc5693bade1092.jpg" }, "width": 3648, "height": 5472 } } },
|
|
252
|
+
{ "image": { "media": { "src": { "id": "8bb438_6bbf8e82fe8f4b79b6e03ee79b66fd6a.jpg" }, "width": 1920, "height": 1280 } } }
|
|
253
|
+
],
|
|
254
|
+
"options": {
|
|
255
|
+
"layout": { "type": "GRID", "horizontalScroll": false, "orientation": "COLUMNS", "numberOfColumns": 3 },
|
|
256
|
+
"item": { "ratio": 1, "crop": "FILL" },
|
|
257
|
+
"thumbnails": { "placement": "NONE" }
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
**COLLAPSIBLE_LIST** — FAQ-style expandable items. Nesting is `COLLAPSIBLE_LIST → COLLAPSIBLE_ITEM → COLLAPSIBLE_ITEM_TITLE | COLLAPSIBLE_ITEM_BODY → PARAGRAPH → TEXT` (body cells may also contain other supported block plugins such as IMAGE or VIDEO). The item wrapper type is **`COLLAPSIBLE_ITEM`** — not `COLLAPSIBLE_LIST_ITEM` or other variants:
|
|
264
|
+
|
|
265
|
+
```json
|
|
266
|
+
{
|
|
267
|
+
"type": "COLLAPSIBLE_LIST",
|
|
268
|
+
"nodes": [
|
|
269
|
+
{
|
|
270
|
+
"type": "COLLAPSIBLE_ITEM",
|
|
271
|
+
"nodes": [
|
|
272
|
+
{
|
|
273
|
+
"type": "COLLAPSIBLE_ITEM_TITLE",
|
|
274
|
+
"nodes": [
|
|
275
|
+
{ "type": "PARAGRAPH", "nodes": [ { "type": "TEXT", "textData": { "text": "Question?", "decorations": [] } } ], "paragraphData": { "textStyle": { "textAlignment": "AUTO" } } }
|
|
276
|
+
]
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
"type": "COLLAPSIBLE_ITEM_BODY",
|
|
280
|
+
"nodes": [
|
|
281
|
+
{ "type": "PARAGRAPH", "nodes": [ { "type": "TEXT", "textData": { "text": "Answer text.", "decorations": [] } } ], "paragraphData": { "textStyle": { "textAlignment": "AUTO" } } }
|
|
282
|
+
]
|
|
283
|
+
}
|
|
284
|
+
]
|
|
285
|
+
}
|
|
286
|
+
],
|
|
287
|
+
"collapsibleListData": {
|
|
288
|
+
"containerData": { "alignment": "CENTER", "textWrap": true },
|
|
289
|
+
"expandOnlyOne": false,
|
|
290
|
+
"initialExpandedItems": "FIRST",
|
|
291
|
+
"direction": "LTR"
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
`initialExpandedItems`: `NONE`·`FIRST`·`ALL`. Set `isQapageData: true` in list metadata when the list should render as FAQ structured data in search results.
|
|
297
|
+
|
|
298
|
+
**HTML** — embeds external content in an iframe. Provide either `url` (page URL) or inline `html` markup; `source` is typically `"HTML"`. Size via `containerData.width` / `height` (`custom` pixel strings or `size`):
|
|
299
|
+
|
|
300
|
+
```json
|
|
301
|
+
{
|
|
302
|
+
"type": "HTML",
|
|
303
|
+
"htmlData": {
|
|
304
|
+
"containerData": { "width": { "custom": "550" }, "height": { "custom": "550" }, "alignment": "CENTER" },
|
|
305
|
+
"url": "https://example.com/embed",
|
|
306
|
+
"source": "HTML"
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
```
|
|
310
|
+
|
|
145
311
|
## Inline text formatting (decorations)
|
|
146
312
|
|
|
147
313
|
Apply formatting with the `decorations` array on a TEXT node. Each decoration is an object with a `type` and (for some types) a data field:
|
|
@@ -169,6 +335,7 @@ Apply formatting with the `decorations` array on a TEXT node. Each decoration is
|
|
|
169
335
|
| `COLOR` | `colorData: { foreground: "#hex" }` (add `background` for highlight) |
|
|
170
336
|
| `LINK` | `linkData: { link: { url, target: "BLANK" } }` |
|
|
171
337
|
| `FONT_SIZE` | `fontSizeData: { unit: "PX", value: 24 }` |
|
|
338
|
+
| `SPOILER` | _(none — `{ "type": "SPOILER" }` hides text behind a reveal control)_ |
|
|
172
339
|
|
|
173
340
|
- **Mixed formatting in one paragraph → split into multiple TEXT nodes** (one per style run) inside the same PARAGRAPH. A single TEXT node carries one consistent set of decorations.
|
|
174
341
|
- Use a plain hex string in `foreground` for colors.
|
|
@@ -229,12 +396,16 @@ Note the mixed-run paragraph at the end: the linked words are their own TEXT nod
|
|
|
229
396
|
|
|
230
397
|
| Parent | Valid children |
|
|
231
398
|
| ------------------------------------ | ----------------------------------------------------------------------------------------------------- |
|
|
232
|
-
| Root `nodes` | PARAGRAPH, HEADING, BULLETED_LIST, ORDERED_LIST, BLOCKQUOTE, DIVIDER, IMAGE, TABLE, CODE_BLOCK
|
|
399
|
+
| Root `nodes` | PARAGRAPH, HEADING, BULLETED_LIST, ORDERED_LIST, BLOCKQUOTE, DIVIDER, IMAGE, TABLE, CODE_BLOCK, BUTTON, AUDIO, VIDEO, GALLERY, COLLAPSIBLE_LIST, HTML |
|
|
233
400
|
| PARAGRAPH / HEADING / CODE_BLOCK | TEXT |
|
|
234
401
|
| BULLETED_LIST / ORDERED_LIST | LIST_ITEM |
|
|
235
402
|
| LIST_ITEM / BLOCKQUOTE | PARAGRAPH (which then contains TEXT) |
|
|
236
403
|
| TABLE → TABLE_ROW → TABLE_CELL | cell contains PARAGRAPH / HEADING / IMAGE |
|
|
237
|
-
| IMAGE
|
|
404
|
+
| IMAGE / VIDEO | CAPTION (optional) |
|
|
405
|
+
| COLLAPSIBLE_LIST | COLLAPSIBLE_ITEM |
|
|
406
|
+
| COLLAPSIBLE_ITEM | COLLAPSIBLE_ITEM_TITLE, COLLAPSIBLE_ITEM_BODY |
|
|
407
|
+
| COLLAPSIBLE_ITEM_TITLE / _BODY | PARAGRAPH (and other plugins supported inside collapsible cells — see COLLAPSIBLE_LIST above) |
|
|
408
|
+
| BUTTON / AUDIO / GALLERY / HTML | _(leaf — `nodes: []` or omit)_ |
|
|
238
409
|
|
|
239
410
|
## Self-audit before returning the document
|
|
240
411
|
|
|
@@ -246,4 +417,10 @@ All decidable from the JSON itself — check before handing the document to a co
|
|
|
246
417
|
4. **Headings carry a `level`** (1–6) and nest logically (don't jump H2 → H4).
|
|
247
418
|
5. **No `\n` inside `textData.text`** — split into sibling nodes; mixed inline formatting → split into multiple TEXT runs.
|
|
248
419
|
6. **Images** use a Wix Media `id` (not a raw URL), with `width`, `height`, and meaningful `altText`.
|
|
249
|
-
7. **Links** — every `LINK` decoration has a valid `url` and `target`.
|
|
420
|
+
7. **Links** — every `LINK` decoration has a valid `url` and `target`. Use a `BUTTON` block with `buttonData.type: "LINK"` for CTAs — not an inline `LINK` decoration styled to look like a button.
|
|
421
|
+
8. **Buttons** — every BUTTON includes **`buttonData.type`** (`LINK` requires `link.url`; `ACTION` has no URL). Omitting `type` is invalid even when `link` is set. `LINK` buttons and inline `LINK` decorations serve different purposes.
|
|
422
|
+
9. **Media blocks** — AUDIO has root-level `nodes: []` and `audioData.containerData`; VIDEO keeps `thumbnail` beside `video` under `videoData`; every GALLERY item is under `galleryData.items` and carries `image.media.src`, `width`, and `height`.
|
|
423
|
+
10. **Collapsible lists** — every item has both `_TITLE` and `_BODY`, each wrapping content through `PARAGRAPH → TEXT`, and every PARAGRAPH has `paragraphData`.
|
|
424
|
+
11. **HTML embeds** — `htmlData` includes `source`, `containerData.width`, `containerData.height`, and either `url` or `html` (not both empty).
|
|
425
|
+
12. **Spoiler** — `SPOILER` is a decoration on `TEXT` (or `containerData.spoiler` on some block plugins in editor output); preserve it verbatim on edit.
|
|
426
|
+
13. **Vertical spacing** — stacked block nodes that should not appear cramped are separated by empty `{ "type": "PARAGRAPH" }` spacers — especially after images, videos, tables, collapsible lists, galleries, buttons, and between consecutive paragraphs. Missing spacers produce a wall-of-blocks layout with no renderer-added margin.
|