@wix/agent-skills 1.31.2 → 1.33.0

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.
Files changed (43) hide show
  1. package/README.md +4 -12
  2. package/package.json +1 -1
  3. package/skills/wix-app/SKILL.md +39 -15
  4. package/skills/wix-app/references/BACKEND_API.md +1 -1
  5. package/skills/wix-app/references/EDITOR_REACT_COMPONENT.md +47 -45
  6. package/skills/wix-app/references/SDK_MODULE_MAP.md +1 -1
  7. package/skills/wix-app/references/STORES_VERSIONING.md +24 -2
  8. package/skills/wix-app/references/dashboard-page/DATA_SOURCES.md +9 -13
  9. package/skills/wix-app/references/dashboard-page/IMAGE_RENDERING.md +61 -0
  10. package/skills/wix-app/references/editor-react-component/ACCESSIBILITY.md +54 -64
  11. package/skills/wix-app/references/editor-react-component/ANIMATED-COMPONENTS.md +25 -27
  12. package/skills/wix-app/references/editor-react-component/COMPONENT-CONTRACT.md +37 -30
  13. package/skills/wix-app/references/editor-react-component/CSS-GUIDELINES.md +43 -29
  14. package/skills/wix-app/references/editor-react-component/DESIGN-STATES.md +42 -42
  15. package/skills/wix-app/references/editor-react-component/DISABLED-INVALID.md +31 -0
  16. package/skills/wix-app/references/editor-react-component/MANIFEST-ERRORS.md +14 -1
  17. package/skills/wix-app/references/editor-react-component/PARTS.md +40 -9
  18. package/skills/wix-app/references/editor-react-component/PROPS-VS-CSS.md +22 -2
  19. package/skills/wix-app/references/editor-react-component/SSR.md +6 -3
  20. package/skills/wix-app/scripts/scan-a11y-eslint.cjs +39 -16
  21. package/skills/wix-app/scripts/sdk-curated.json +45 -0
  22. package/skills/wix-app/scripts/sdk-lookup.cjs +1180 -0
  23. package/skills/wix-base44-connector/SKILL.md +4 -2
  24. package/skills/wix-base44-connector/scripts/utils.cjs +5 -2
  25. package/skills/wix-headless-kit/SKILL.md +4 -1
  26. package/skills/wix-headless-kit/guides/api-run.md +14 -20
  27. package/skills/wix-headless-kit/install/pins.json +1 -1
  28. package/skills/wix-manage/SKILL.md +30 -8
  29. package/skills/wix-manage/references/automations/actions.md +134 -0
  30. package/skills/wix-manage/references/automations/activation-status.md +64 -48
  31. package/skills/wix-manage/references/automations/api-catalog.md +213 -77
  32. package/skills/wix-manage/references/automations/automation-model.md +199 -0
  33. package/skills/wix-manage/references/automations/bracket-expressions.md +132 -0
  34. package/skills/wix-manage/references/automations/build-and-manage-automations.md +305 -26
  35. package/skills/wix-manage/references/automations/triggers.md +190 -0
  36. package/skills/wix-manage/references/collaborators/manage-site-collaborators.md +128 -0
  37. package/skills/wix-manage/references/forms/forms-dashboard-navigation.md +3 -1
  38. package/skills/wix-manage/references/forms/read-forms-and-submissions.md +277 -0
  39. package/skills/wix-manage/references/forms/update-form.md +5 -0
  40. package/skills/wix-manage/references/seo/manage-url-redirects.md +33 -4
  41. package/skills/wix-manage/references/sites/create-headless-site.md +14 -5
  42. package/skills/wix-manage/references/sites/upload-static-site.md +373 -302
  43. package/skills/wix-vibe-headless/references/shared/app/components/WixManageBanner.jsx +3 -3
@@ -3,7 +3,7 @@
3
3
  Use this reference when a named part changes appearance through interaction or
4
4
  a selectable value. A design state styles an element differently for an
5
5
  eligible interaction (`hover`, `focus`, `disabled`, `invalid`) or per a
6
- selectable value in its data (`selected`, `active`, `open`, …). Author states
6
+ selectable value in its data (`selected`, `active`, `checked`, …). Author states
7
7
  in the component's `.tsx` and `.module.css`.
8
8
 
9
9
  ## Contents
@@ -23,10 +23,11 @@ in the component's `.tsx` and `.module.css`.
23
23
  | Interactive — `button`/`a`/`input`/`select`/`textarea`/`summary`, an interactive `role`, or an interactive handler (`onClick`/`onMouseEnter`/`onFocus`/…) | `hover` |
24
24
  | 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` |
25
25
  | Other interactive element whose editable focus appearance the user explicitly requests | `focus` |
26
- | Disableable — `button`/`input`/`select`/`textarea`/`fieldset` or a disableable role | `disabled` (+ `invalid` for `input`/`select`/`textarea`) |
27
- | Has a selectable/variant value in its data — `selected`/`active`/`current`/`open`/`expanded`/`checked`/`featured` | that custom state |
28
- | Whole component opens/collapses (expandable panel, disclosure, dropdown, drawer), even when a click toggles it | root `isOpen` prop state — see §6 |
29
- | None of the above | no states — resting style only |
26
+ | `button`/`input`/`select`/`textarea`/`fieldset` when asked or its action can be unavailable | `disabled` |
27
+ | `input`/`select`/`textarea` with a constraint or `setCustomValidity`; a `form`/`fieldset` with one | `invalid` |
28
+ | Has a selectable/variant value in its data — `selected`/`active`/`current`/`checked`/`featured` | that custom state |
29
+ | Whole component opens/collapses or reveals content (expandable panel, disclosure, dropdown, drawer, popup, tooltip) on click, hover or focus | root `isOpen` state, no part `open` state (§6) |
30
+ | None of the above, incl. a non-interactive root or part | no states, and no `:hover`/`:active`/`:focus` rules, even decorative; resting style only |
30
31
 
31
32
  An explicit `role` overrides the tag's implicit semantics: `<input role="button">`
32
33
  is a non-input control, while `<div role="checkbox">` is an input field.
@@ -37,8 +38,7 @@ unless the user explicitly requests editable focus styling. A control that
37
38
  restyles its own outline, background, or border must still carry a standalone
38
39
  `:focus-visible` rule as its keyboard affordance.
39
40
 
40
- A custom state may also be driven by a root-level boolean prop (e.g.
41
- `isFeatured`) instead of by markup or item data — see §5. For an active-item
41
+ A root-level boolean prop can also drive a custom state (§5). For an active-item
42
42
  component, drive each item's `--active` class by comparing its index with the
43
43
  active-index prop rather than storing a per-item boolean.
44
44
 
@@ -47,7 +47,7 @@ active-index prop rather than storing a per-item boolean.
47
47
  Flat: the element's own global class + `--<state>`. Because inner-part
48
48
  global classes are prefixed with the component name, the state class is prefixed too —
49
49
  e.g. `pricing-card-cta--hover`, `pricing-card-plan-row--selected`. Never bare
50
- (`cta--hover` would collide with other components on the page) and never
50
+ (`cta--hover` collides with other components) and never
51
51
  nested (`card__row--selected`).
52
52
 
53
53
  ## 3. Author CSS
@@ -56,7 +56,7 @@ Put the resting value in the bare class; put only the state override in the
56
56
  state selector. When both set the same property, write the state selector
57
57
  **before** the bare class. Manifest generation can otherwise record the state
58
58
  value as the part's resting `defaultValue`, even though runtime CSS renders
59
- correctly. The state selector still wins at runtime through its specificity.
59
+ correctly.
60
60
 
61
61
  - **Native design state** — pair the pseudo-class with the `:global` modifier.
62
62
  Every exposed native state needs both selectors in the same rule; do not
@@ -101,34 +101,32 @@ The local static accessibility scan cannot measure rendered color contrast.
101
101
  - **Native** — render the correct interactive element (`<button>`, an
102
102
  interactive `role`, or a handler). No custom state class is needed, but the
103
103
  element must still satisfy the accessibility contract for its semantics.
104
- - **Custom** — toggle the global state class from the element's data. Every
105
- state class toggled in TSX needs a matching `:global()` rule in §3; without
106
- one the manifest generator silently drops the state.
104
+ - **Custom** — toggle the global state class from the element's data, except
105
+ for open/collapsed content (§6). Every state class toggled in TSX needs a
106
+ matching `:global()` rule in §3; without one the manifest generator silently
107
+ drops the state.
107
108
  - **Inner elements** — every named inner element gets an `elementProps` entry;
108
109
  spread it so editor-driven states reach it. On a raw HTML element also merge
109
110
  `elementProps?.<key>.className` inline; on a skill-built sub-component the
110
- spread alone suffices (it merges `className` itself). The `elementProps` key
111
- stays the **short** part name (`cta`) even though the element's global class
112
- is prefixed (`pricing-card-cta`) — don't rename the key to match the class.
113
-
114
- Wire native and custom states as follows:
111
+ spread alone suffices (it merges `className` itself). The key is the global
112
+ class in camelCase (`COMPONENT-CONTRACT.md`).
115
113
 
116
114
  ```tsx
117
115
  <button
118
116
  type="button"
119
- {...elementProps?.cta}
120
- className={classNames('pricing-card-cta', styles.cta, elementProps?.cta?.className)}
117
+ {...elementProps?.pricingCardCta}
118
+ className={classNames('pricing-card-cta', styles.cta, elementProps?.pricingCardCta?.className)}
121
119
  >
122
120
  {label}
123
121
  </button>
124
122
 
125
123
  <li
126
- {...elementProps?.planRow}
124
+ {...elementProps?.pricingCardPlanRow}
127
125
  className={classNames(
128
126
  'pricing-card-plan-row',
129
127
  styles.planRow,
130
128
  row.selected && 'pricing-card-plan-row--selected',
131
- elementProps?.planRow?.className,
129
+ elementProps?.pricingCardPlanRow?.className,
132
130
  )}
133
131
  >
134
132
  {row.label}
@@ -143,10 +141,9 @@ A **prop-triggered** state is a custom state switched by a single boolean
143
141
  whole component's appearance — e.g. `isLoading`, `isFeatured`.
144
142
 
145
143
  Mark the prop with the `ElementState<boolean>` type from
146
- `@wix/react-component-utils`. It is an identity alias — the prop still behaves
147
- as a plain `boolean` at runtime and stays a normal boolean in the component's
148
- data — but the manifest generator detects the marker and emits a custom state
149
- on the **root element**.
144
+ `@wix/react-component-utils`. It is an identity alias (a plain `boolean` at
145
+ runtime and in data); the manifest generator detects the marker and emits a
146
+ custom state on the **root element**.
150
147
 
151
148
  ```tsx
152
149
  import type { ElementState } from '@wix/react-component-utils';
@@ -178,39 +175,41 @@ Rules:
178
175
  ```
179
176
 
180
177
  Prefer a native state (interactive markup) or a class trigger (per-item data
181
- such as `row.selected`) whenever one fits — those are the common cases and work
182
- at any depth. Reach for a prop trigger only for a root-level boolean switch
183
- or an open/collapsed toggle (§6).
178
+ such as `row.selected`) whenever one fits; both work at any depth. Reach for a
179
+ prop trigger only for a root-level boolean switch.
184
180
 
185
181
  ## 6. Expose Open/Collapsed Toggles
186
182
 
187
- For a component that opens or collapses, expose its state to the editor even
188
- when a click toggles it. `useState` alone gives the manifest no state picker.
189
- For one-of-many item bodies, use the active-item contract instead.
183
+ Expose the open state of a component that opens, collapses or reveals content
184
+ on click, hover or focus. `useState` alone, or a CSS `:hover`/`:focus-within`
185
+ reveal, gives the editor no state picker. For one-of-many item bodies, use the
186
+ active-item contract instead.
190
187
 
191
188
  - Add `isOpen?: ElementState<boolean>` and set `isOpen: false` in
192
- `defaultProps`.
193
- - Seed and resync internal state from the prop; clicks update internal state.
189
+ `defaultProps`. Keep it props-only (§5).
190
+ - Seed and resync internal state from the prop; clicks, pointer enter/leave and
191
+ focus/blur update it.
194
192
  - Always render the collapsible content; hide it with CSS plus `aria-hidden`
195
193
  and `inert` when closed. Never mount it with `open && …`.
196
- - Style the open state with inner-part classes
197
- (`<component-name>-content--open`) and matching `:global()` rules, not only
198
- inline styles or refs measured during render.
199
- - Keep the root state props-only for the on-stage picker (§5).
194
+ - Style the open look with a module class (`open && styles.contentOpen`), not
195
+ only inline styles or render-time refs.
196
+ - Never toggle a `:global()` class for it (`--open`, `--visible`, …) on the
197
+ root or any part: each adds an "Open" design-panel state beside the on-stage
198
+ picker.
200
199
 
201
200
  ```tsx
202
201
  const [open, setOpen] = React.useState(Boolean(isOpen));
203
202
  React.useEffect(() => setOpen(Boolean(isOpen)), [isOpen]);
204
203
 
205
204
  <div
206
- {...elementProps?.content}
205
+ {...elementProps?.infoPanelContent}
207
206
  aria-hidden={!open}
208
207
  {...(!open && { inert: '' })}
209
208
  className={classNames(
210
209
  'info-panel-content',
211
210
  styles.content,
212
- open && 'info-panel-content--open',
213
- elementProps?.content?.className,
211
+ open && styles.contentOpen,
212
+ elementProps?.infoPanelContent?.className,
214
213
  )}
215
214
  >
216
215
  <p>{text}</p>
@@ -219,7 +218,7 @@ React.useEffect(() => setOpen(Boolean(isOpen)), [isOpen]);
219
218
 
220
219
  ```css
221
220
  .content { display: none; }
222
- .content:global(.info-panel-content--open) { display: block; }
221
+ .contentOpen { display: block; }
223
222
  ```
224
223
 
225
224
  ## Checklist
@@ -233,4 +232,5 @@ React.useEffect(() => setOpen(Boolean(isOpen)), [isOpen]);
233
232
  - [ ] Every state class toggled in TSX has a matching `:global()` CSS rule.
234
233
  - [ ] `ElementState<boolean>` is used only for a component-level root state.
235
234
  - [ ] An open/collapsed component exposes a props-only `isOpen` root state,
236
- syncs internal state from it, and always renders its content.
235
+ syncs internal state from it, always renders its content, and adds no
236
+ `--open` class anywhere.
@@ -0,0 +1,31 @@
1
+ # Disabled and Invalid Wiring
2
+
3
+ Use after DESIGN-STATES.md §1 decides a part exposes `disabled` or `invalid`. A
4
+ native element's `:disabled` or `:invalid`, paired with the `:global` modifier in
5
+ the §3 rule, is what generates the state.
6
+
7
+ ## Disabled
8
+
9
+ Author it when asked, or when the control stays visible but its action can be
10
+ unavailable (unmet precondition, request in progress, sold out). Otherwise author
11
+ no `disabled` binding and no `--disabled` rule.
12
+
13
+ ## Invalid
14
+
15
+ Author it only for a native field with a constraint (`required`, `pattern`,
16
+ `type="email"`, …) or a `setCustomValidity` message. An unconstrained field is
17
+ never invalid.
18
+
19
+ For validity from outside the field (a server check, a password confirmation),
20
+ call `setCustomValidity` in the component file, on the field itself: through a
21
+ ref bound with `ref={confirmRef}`, a callback ref, or `event.currentTarget` in an
22
+ `on*` handler (inline, or a function of the component passed by name). The field may be in a component of the same file
23
+ that renders it. Not from a custom hook, a function outside the component, or
24
+ another file, and not through a ref given to a custom component. A field in a loop
25
+ or rendered conditionally is detected when every field with that `ref` or handler
26
+ prop calls it. If only some do, the ones that do may not be detected: give them a
27
+ constraint prop instead, otherwise leave `invalid` off them.
28
+
29
+ A `form` or `fieldset` is invalid when a field inside it is
30
+ (`.form:global(.signup-form--invalid), .form:invalid`), with no constraint of its
31
+ own; with no constrained field it is never invalid.
@@ -1,7 +1,7 @@
1
1
  # Manifest Build Errors
2
2
 
3
3
  Use this reference only when `npx wix build` or `npx wix generate manifest`
4
- exits with an error. Read the error name from the first token before the colon
4
+ exits with an error or prints a warning. For an error, read the error name from the first token before the colon
5
5
  (`IoError:`, `ParseError:`, or `NotFoundError:`), then apply the matching fix.
6
6
 
7
7
  | Error | Fix |
@@ -20,6 +20,19 @@ exits with an error. Read the error name from the first token before the colon
20
20
 
21
21
  After each fix, re-run `npx wix build && npx wix generate manifest` to confirm.
22
22
 
23
+ ## Warnings
24
+
25
+ The build can also print non-fatal warnings such as
26
+ `[css] … styles selectors that matched no element in the extraction render`.
27
+ Read every warning and fix it; this is mandatory, with one exception below.
28
+
29
+ - **A part's own class** (`.errorMessage`, `.item`) means the part is not
30
+ mounted in the extraction render. Keep it mounted per `PARTS.md`, then
31
+ re-run build and manifest generation.
32
+ - **A class applied only by state** (`.contentOpen` from `open && styles.contentOpen`)
33
+ is expected: the part is mounted and only the state look is absent. Leave the
34
+ markup and CSS unchanged; do not force the state on or remove the styling.
35
+
23
36
  ## Checklist
24
37
 
25
38
  - [ ] The fix matches the reported error name and message.
@@ -24,13 +24,14 @@ would plausibly control independently in the editor. It receives:
24
24
 
25
25
  - one prefixed global class: `'<component-name>-<part-name>'`
26
26
  - one CSS Module class
27
- - one matching `elementProps` entry, spread onto the element. Declare `a11y` on
28
- the entry only when the part reads `ariaLabel`, and destructure it out before
29
- spreading (see `ACCESSIBILITY.md`)
27
+ - one matching `elementProps` entry, keyed by the camelCase of its global class
28
+ and spread onto the element. Declare `a11y` on the entry only when the part
29
+ reads `ariaLabel`, and destructure it out before spreading (see
30
+ `ACCESSIBILITY.md`)
30
31
 
31
32
  Apply this filter to every candidate:
32
33
 
33
- - A state or variant (`selected`, `active`, `open`, `disabled`) is not a part.
34
+ - A state or variant (`selected`, `active`, `checked`, `disabled`) is not a part.
34
35
  Implement it as a prefixed design-state modifier on the affected part.
35
36
  - A hidden and shown version of the same element is one part, not two.
36
37
  - A grouping or layout-only wrapper is not a part. A wrapper that isn't
@@ -45,13 +46,43 @@ Apply this filter to every candidate:
45
46
  share the same semantic editor surface. Distinguish position with data or a
46
47
  module-only helper class, not separate part names.
47
48
 
48
- The manifest generator can silently omit a named part behind conditional JSX.
49
- Keep an editor-controlled part mounted in the render it inspects. Hide it with
50
- CSS driven by a data attribute, or disable it when it should remain visible.
49
+ ## Keep Every Part Mounted
50
+
51
+ The manifest is generated from a single server-side render of the component
52
+ with mock props. Effects do not run and `useState` keeps its initial value, so
53
+ markup that is not mounted in that render, and the CSS that styles it, never
54
+ reaches the manifest. The source is not changed afterwards: write it so every
55
+ part is mounted regardless of state.
56
+
57
+ - Mount every phase (loading, error, empty, question, result) and hide the
58
+ inactive ones with the `hidden` attribute. Never write
59
+ `phase === 'x' && <Part />`. Give a `hidden` element no author `display`
60
+ rule: it would override `hidden`.
61
+ - While data is missing, render one static placeholder item inside a hidden
62
+ wrapper so the item parts exist. It is never visible.
63
+ - Mutually exclusive messages (loading, error, empty) are one part whose text
64
+ switches by phase, not several parts with the same name.
65
+ - A modifier toggled by state (`correct`, `incorrect`) is a design state; see
66
+ `DESIGN-STATES.md`.
67
+
68
+ ```tsx
69
+ const item = current ?? placeholderItem; // placeholder shows only while hidden
70
+
71
+ <div hidden={phase !== 'question'}>
72
+ <h3
73
+ {...elementProps?.quizQuestion}
74
+ className={classNames('quiz-question', styles.question, elementProps?.quizQuestion?.className)}
75
+ >
76
+ {item.question}
77
+ </h3>
78
+ </div>
79
+ ```
80
+
51
81
  After generating the manifest, confirm that its `elements` include every
52
- intended named part.
82
+ intended named part and that each `elementProps.<key>` read in code equals the
83
+ camelCase of that part's global class.
53
84
 
54
- ## Plausibility Check
85
+ ## Sanity Check
55
86
 
56
87
  For each candidate, ask:
57
88
 
@@ -31,11 +31,11 @@ Use this pattern:
31
31
 
32
32
  ```tsx
33
33
  <div
34
- {...elementProps?.progressBar}
34
+ {...elementProps?.audioPlayerProgressBar}
35
35
  className={classNames(
36
36
  'audio-player-progress-bar',
37
37
  styles.progressBar,
38
- elementProps?.progressBar?.className,
38
+ elementProps?.audioPlayerProgressBar?.className,
39
39
  )}
40
40
  >
41
41
  {/* progress UI */}
@@ -51,6 +51,26 @@ the interaction contract. `pauseButtonVisibility` in an autoplaying component
51
51
  is the canonical exception: the editor preview must be able to force the
52
52
  play/pause control visible.
53
53
 
54
+ ## Lists of Groups
55
+
56
+ When each item of a list holds its own list, the Manage panel can't edit the inner list. Use fixed
57
+ groups instead and map them internally:
58
+
59
+ - Give each group flat top-level fields named after the group, so Manage
60
+ Items shows "Starters Dishes", not "Dishes 1".
61
+ - For a requested range ("2 to 5 sections"), create the maximum and add a
62
+ count prop that shows the first N.
63
+ - Group order is fixed. If the user asks to reorder groups, don't add an
64
+ order, slot, key or label field or a separate array to emulate it; say in
65
+ your reply that groups can't be reordered.
66
+
67
+ ```ts
68
+ // startersTitle: string; startersDishes: Array<Dish>
69
+ // /** @min 2 @max 5 */ sectionCount: number
70
+ // Not: sections: Array<{ dishes: Array<Dish> }>, starters: Section,
71
+ // or sections: Array<{ slot: number }> with dishes1…dishes5
72
+ ```
73
+
54
74
  ## Examples
55
75
 
56
76
  ### Content and Behavior Props
@@ -29,9 +29,11 @@ const items = buildItems(props.items);
29
29
  return <ul>{items.map(renderItem)}</ul>;
30
30
  ```
31
31
 
32
- This does not require every data branch to render simultaneously. Empty,
33
- loading, and error states may be conditional when their initial value is stable
34
- and the same on server and client.
32
+ This does not require every data branch to render simultaneously: loading,
33
+ empty, and error states may be conditional when their initial value is stable
34
+ and the same on server and client. A branch that contains a named part must
35
+ still be mounted in the first render, or the manifest omits that part (see
36
+ `PARTS.md`).
35
37
 
36
38
  ## Deterministic Output
37
39
 
@@ -47,4 +49,5 @@ format, or a client-only enhancement that does not replace the initial markup.
47
49
 
48
50
  - [ ] No browser globals are accessed at module scope or during render.
49
51
  - [ ] Initial server/client markup is complete and identical.
52
+ - [ ] Every named part is mounted in the first render.
50
53
  - [ ] Render output does not depend on random, clock, locale, or timezone state.
@@ -61,19 +61,23 @@ const SDK_HANDLER_PROPS = {
61
61
  onDoubleClick: 'onDblClick',
62
62
  onMouseEnter: 'onMouseIn',
63
63
  onMouseLeave: 'onMouseOut',
64
+ onFocus: 'onFocus',
65
+ onBlur: 'onBlur',
64
66
  };
65
67
  const isSdkRoot = (tag) => /\bid=\{\s*(?:props\.)?id\s*\}/.test(tag);
66
68
 
67
- /** Every handler on the tag forwards its SDK prop, `onClick={onClick}` or `onMouseEnter={props.onMouseIn}`; anything else makes the root a control. */
69
+ const getHandlerAttributes = (tag) => [...tag.matchAll(/\b(on[A-Z]\w*)=\{([^}]*)\}/g)];
70
+
71
+ /** `onClick={onClick}` or `onMouseEnter={props.onMouseIn}`. */
72
+ function isSdkForward(name, value) {
73
+ const sdkProp = SDK_HANDLER_PROPS[name];
74
+ return Boolean(sdkProp) && value.trim().replace(/^props\./, '') === sdkProp;
75
+ }
76
+
77
+ /** Every handler on the tag forwards its SDK prop; anything else makes the root a control. */
68
78
  function forwardsOnlySdkHandlers(tag) {
69
- const handlers = [...tag.matchAll(/\b(on[A-Z]\w*)=\{([^}]*)\}/g)];
70
- return (
71
- handlers.length > 0 &&
72
- handlers.every(([, name, value]) => {
73
- const sdkProp = SDK_HANDLER_PROPS[name];
74
- return Boolean(sdkProp) && value.trim().replace(/^props\./, '') === sdkProp;
75
- })
76
- );
79
+ const handlers = getHandlerAttributes(tag);
80
+ return handlers.length > 0 && handlers.every(([, name, value]) => isSdkForward(name, value));
77
81
  }
78
82
 
79
83
  const PASSIVE_AUTOPLAY_HANDLERS = new Set([
@@ -85,11 +89,26 @@ const PASSIVE_AUTOPLAY_HANDLERS = new Set([
85
89
  'onBlurCapture',
86
90
  ]);
87
91
 
88
- /** Carousel roots observe hover/focus to pause autoplay; these handlers do not activate the region. */
92
+ /**
93
+ * Carousel roots observe hover and focus to pause autoplay; these handlers do not activate
94
+ * the region. A root needs both, so hover-only roots stay flagged. SDK forwards may sit
95
+ * beside them; any other handler makes the root a control.
96
+ */
89
97
  function pausesAutoplayOnly(tag) {
90
- if (!isSdkRoot(tag) || !tag.includes('data-pause-button-visibility=')) return false;
91
- const handlers = [...tag.matchAll(/\b(on[A-Z]\w*)=\{[^}]*\}/g)];
92
- return handlers.length > 0 && handlers.every(([, name]) => PASSIVE_AUTOPLAY_HANDLERS.has(name));
98
+ if (!isSdkRoot(tag)) return false;
99
+ const handlers = getHandlerAttributes(tag);
100
+ // `onMouseEnter={onMouseIn}`, `onMouseEnter={onMouseEnter}` or `onFocus={onFocus}` forwards a prop; it is not a pause observer.
101
+ const isPauseObserver = (name, value) =>
102
+ PASSIVE_AUTOPLAY_HANDLERS.has(name) &&
103
+ !isSdkForward(name, value) &&
104
+ value.trim().replace(/^props\./, '') !== name;
105
+ const hasPauseObserver = (pattern) =>
106
+ handlers.some(([, name, value]) => pattern.test(name) && isPauseObserver(name, value));
107
+ return (
108
+ hasPauseObserver(/^onMouse/) &&
109
+ hasPauseObserver(/^on(?:Focus|Blur)/) &&
110
+ handlers.every(([, name, value]) => isPauseObserver(name, value) || isSdkForward(name, value))
111
+ );
93
112
  }
94
113
 
95
114
  /** Roles whose native element cannot express a styled component. */
@@ -112,9 +131,13 @@ function roleHasNoNativeTag(tag) {
112
131
  */
113
132
  const EXEMPTIONS = [
114
133
  {
115
- // ANIMATED-COMPONENTS.md requires the noninteractive carousel root to
116
- // observe hover and focus so autoplay stops while a visitor is using it.
117
- rules: new Set(['jsx-a11y/no-noninteractive-element-interactions']),
134
+ // ANIMATED-COMPONENTS.md requires the carousel root to observe hover and
135
+ // focus so autoplay pauses while a visitor is using it.
136
+ rules: new Set([
137
+ 'jsx-a11y/click-events-have-key-events',
138
+ 'jsx-a11y/no-noninteractive-element-interactions',
139
+ 'jsx-a11y/no-static-element-interactions',
140
+ ]),
118
141
  tag: pausesAutoplayOnly,
119
142
  },
120
143
  {
@@ -0,0 +1,45 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "about": "Choices no SDK index can make. sdk-lookup.cjs prints a rule when one of its entities matches. `choice: site` means the namespace depends on the site, not on the install.",
4
+ "rules": [
5
+ {
6
+ "entities": ["product", "products", "catalog"],
7
+ "namespaces": ["products", "productsV3"],
8
+ "choice": "site",
9
+ "note": "Call catalogVersioning.getCatalogVersion() first: V3 sites use productsV3, V1 sites use products. Support both paths, and request both scope sets plus SCOPE.STORES.CATALOG_READ_LIMITED. To search by name, V3 has productsV3.searchProducts() (queryProducts cannot filter on name); V1 has no search method, and its builder takes name only with startsWith.",
10
+ "source": "SKILL.md (Stores), references/STORES_VERSIONING.md, references/stores/QUERY.md"
11
+ },
12
+ {
13
+ "entities": ["inventory"],
14
+ "namespaces": ["inventory", "inventoryItemsV3"],
15
+ "choice": "site",
16
+ "note": "Call catalogVersioning.getCatalogVersion() first: V3 sites use inventoryItemsV3, V1 sites use inventory. Request both scope sets.",
17
+ "source": "references/STORES_VERSIONING.md"
18
+ },
19
+ {
20
+ "entities": ["refund", "refunds"],
21
+ "namespaces": ["orders", "orderTransactions", "refunds"],
22
+ "choice": { "prefer": "orders", "intents": ["search", "read"] },
23
+ "note": "Refunded orders: orders.searchOrders with a paymentStatus filter, then orderTransactions.listTransactionsForMultipleOrders(ids) once for the visible page. Refund events are the exception: they live in @wix/payments (namespace refunds), not in @wix/ecom.",
24
+ "source": "references/dashboard-page/DATA_SOURCES.md, references/backend-event/COMMON-EVENTS.md"
25
+ },
26
+ {
27
+ "entities": ["location", "locations"],
28
+ "namespaces": ["locations"],
29
+ "note": "queryLocations is admin-only although its scope is SCOPE.DC-MULTILOCATION.READ-LOCATIONS: route it to a backend endpoint and elevate it there.",
30
+ "source": "moved from SKILL.md (Identity and Elevation Requirement), which links here"
31
+ },
32
+ {
33
+ "entities": ["member", "members"],
34
+ "namespaces": ["members"],
35
+ "note": "getMember and queryMembers withhold PRIVATE members from visitor and member callers: never elevate them to fill a sparse result.",
36
+ "source": "SKILL.md (Identity and Elevation Requirement)"
37
+ },
38
+ {
39
+ "entities": ["cart", "currentcart"],
40
+ "namespaces": ["currentCartV2", "cartV2"],
41
+ "note": "currentCartV2.addLineItemsToCurrentCart is visitor-callable although its scope is SCOPE.ECOM.MANAGE-ADMIN. Never elevate it.",
42
+ "source": "moved from SKILL.md (Identity and Elevation Requirement), which links here"
43
+ }
44
+ ]
45
+ }