@wix/agent-skills 1.25.0 → 1.26.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wix/agent-skills",
3
- "version": "1.25.0",
3
+ "version": "1.26.0",
4
4
  "description": "Wix Skills for AI coding agents — versioned npm distribution of the wix/skills content.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -24,12 +24,12 @@ Helps build extensions for Wix CLI applications. Covers all extension types: das
24
24
  - [ ] **Step 2:** Read extension reference file(s) for the chosen type(s) and the project-wide [CODE_QUALITY.md](references/CODE_QUALITY.md)
25
25
  - [ ] **Dashboard page UI:** Translated the prompt into a workflow before choosing components — what the user must understand, focus on, investigate, act on, and see confirmed. See [UX Success Model](references/dashboard-page/UX_SUCCESS_MODEL.md), and the installed package's own `Collection Toolkit.md` guide for which component serves each need ([The Discovery Chain](references/WIX_PATTERNS_DOCS.md#the-discovery-chain)).
26
26
  - [ ] **No `SummaryBar` unless the request asked for one** — a named total, count, or "how many / how much" figure in the prompt. Not "the page seems like it wants one": an uninvited bar pushes the rows down and puts a number on screen nobody asked to be right about. When the request did ask, the number has to come from something that counts ([QUERY_AND_PAGING.md](references/dashboard-page/QUERY_AND_PAGING.md#what-fetchtotal-is-allowed-to-call)) and be read through `useSelector`, because the state is MobX ([TABLE_STATE.md](references/dashboard-page/TABLE_STATE.md#reading-state-outside-the-table-it-is-mobx)).
27
- - [ ] **A row the user can open, as a page** — `navigateToEntityPage` to an `EntityPage` when the record is editable, or to a read-only detail route when it isn't ([DRAFT_TEMPLATE_ROUTER.md § 4](references/dashboard-page/DRAFT_TEMPLATE_ROUTER.md#4-read-only-detail-route--case-a)) — unless the prompt is explicitly a report or an export. **Never a `SidePanel`**: in Cairo that hosts a page's own panels, and no `example-bm` collection opens a row into one. The read-only route is a WDS `Page`, because `EntityPage` has no view mode — verified at 1.471.0, where `fetch` and `onSave` are both required and `ActionsBarConfig` can retext Save but not remove it.
27
+ - [ ] **A row the user can open, as a page** — `navigateToEntityPage` to an `EntityPage` when the record is editable, or to a read-only detail route when it isn't (the package's `Collection and Read-Only Detail Template`, reached from [DRAFT_TEMPLATE.md](references/dashboard-page/DRAFT_TEMPLATE.md)) — unless the prompt is explicitly a report or an export. **Never a `SidePanel`**: in Cairo that hosts a page's own panels. The template's page says why the read-only route is a WDS `Page` rather than an `EntityPage`.
28
28
  - [ ] **Every filter reaches the query**: declared in the collection hook's `filters` and read inside `fetchData`. Filter UI that never narrows the rows is a defect that looks like a feature.
29
29
 
30
30
  A filtered table with no drill-in and no working filters is what gets built when nobody states the requirement — the most common way a generated dashboard disappoints. The aggregate is the judgment call; the drill-in and the filters are not.
31
- - [ ] **🛑 Template-First Gate (MANDATORY, dashboard UI only, comes before writing any shell/provider/router):** Identified which case in [DRAFT_TEMPLATE.md](references/dashboard-page/DRAFT_TEMPLATE.md#which-case-matches-the-request) — A (collection only), B (collection + entity), C (settings only), or D (all three) — matches what Step 2's workflow analysis above just established — then copied and adapted that case's files. Composing the page shell, provider nesting, or router wiring from scratch when a case already shows it is the failure mode this gate exists to prevent — the Patterns/Component Docs gates below are for what the matching case's files don't cover, not a replacement for starting there. The reverse holds too: a case replaces composing the shell, never the discovery chain, so every symbol the case doesn't show still goes through the next gate.
32
- - [ ] **🛑 Patterns Docs Gate (MANDATORY for any dashboard page UI):** Read [WIX_PATTERNS_DOCS.md](references/WIX_PATTERNS_DOCS.md) **before the first command that touches `node_modules/@wix/patterns`** — its discovery chain (`pkg-root.cjs`, the index, `Composition and Providers.md` once, `Collection Toolkit.md` for which component serves a need) lives only in that file, and this line is a summary of its step 1, not a substitute for opening it. Then **probe** `dist/docs/index.json` with `grep`/`python3` — never a whole-file `Read`, which truncates it silently. It is the one file that says, per symbol, where to import it from (`importPath`), whether its props live in the doc or in a `.d.ts` (`bundle`), and which worked examples exist (`examples`). Upgrade `@wix/patterns` if that file is missing. Patterns API facts come only from the published `dist/docs/` (pages), `dist/examples/` (worked calls) and `dist/dts-bundle/` (types) trees — never from `src/`, `dist/esm/`, or any other path inside the package, with one named exception: `dist/types/` when a bundle has stubbed the prop you need (WIX_PATTERNS_DOCS.md step 5).
31
+ - [ ] **🛑 Template-First Gate (MANDATORY, dashboard UI only, comes before writing any shell/provider/router):** Followed [DRAFT_TEMPLATE.md](references/dashboard-page/DRAFT_TEMPLATE.md): found the page templates the installed `@wix/patterns` ships, chose one with its `Page Templates.md` guide — which pages the workflow above needs, **and where the rows come from** (your own fetch, or a CMS collection) — read that template's page, and copied every file in its `templateFiles`. Composing the page shell, provider nesting, or router wiring from scratch when a template already shows it is the failure mode this gate exists to prevent — the Patterns/Component Docs gates below are for what the template doesn't cover, not a replacement for starting there. The reverse holds too: a template replaces composing the shell, never the discovery chain, so every symbol it doesn't show still goes through the next gate.
32
+ - [ ] **🛑 Patterns Docs Gate (MANDATORY for any dashboard page UI):** Read [WIX_PATTERNS_DOCS.md](references/WIX_PATTERNS_DOCS.md) **before the first command that touches `node_modules/@wix/patterns`** — its discovery chain (`pkg-root.cjs`, the index, `Composition and Providers.md` once, `Collection Toolkit.md` for which component serves a need) lives only in that file, and this line is a summary of its step 1, not a substitute for opening it. Then **probe** `dist/docs/index.json` with `grep`/`python3` — never a whole-file `Read`, which truncates it silently. It is the one file that says, per symbol, where to import it from (`importPath`), whether its props live in the doc or in a `.d.ts` (`bundle`), and which worked examples exist (`examples`). Upgrade `@wix/patterns` if that file is missing. Patterns API facts come only from the published `dist/docs/` (pages), `dist/examples/` (worked calls), `dist/dts-bundle/` (types) and `dist/templates/` (whole pages) trees — never from `src/`, `dist/esm/`, or any other path inside the package, with one named exception: `dist/types/` when a bundle has stubbed the prop you need (WIX_PATTERNS_DOCS.md step 5).
33
33
  - [ ] **🛑 Component Docs Gate (MANDATORY, dashboard UI only):** For each patterns symbol you are about to write, decided **from the index** which single artifact answers the question you actually have — `importPath`, `examples`, or `bundle` — and read only that one, per [Component Selection Order](#component-selection-order)'s "the short version". State which artifact you read per symbol, and why, before the first line of JSX. Reading a doc *and* its bundle for the same symbol, or opening a page for an `importPath` the index already gave you, is the failure this gate exists to prevent.
34
34
 
35
35
  For the object `useTableCollection()` returns, read [TABLE_STATE.md](references/dashboard-page/TABLE_STATE.md) — a state object you receive rather than construct, whose members are unobvious and several plausible ones absent.
@@ -45,7 +45,7 @@ Helps build extensions for Wix CLI applications. Covers all extension types: das
45
45
  - [ ] Invoked `wix-design-system` skill ONLY before editing the first `.tsx`/`.jsx` file that imports `@wix/design-system`. Skip for backend-only or data-only extensions.
46
46
  - [ ] WDS: the design-system stylesheets are imported in exactly one place per app — `BusinessManagerTheme.tsx` for a dashboard surface (see the next item), or the main component entry file for a site/editor extension. Never in child, tab or helper files, and never twice.
47
47
  - [ ] **🛑 Business Manager theme (every dashboard surface — page, modal AND plugin):** wrote `BusinessManagerTheme.tsx` once (both stylesheets, including `themes/odeditor.global.css`, plus `WixDesignSystemProvider` → `WixDesignSystemIconThemeProvider` → `IconThemeProvider theme="odeditor"` → `WixDesignSystemDefaultPropsProvider`), and wrapped **each** extension's root in it — above `WixPatternsProvider` and above `CustomModalLayout`. Every extension is a separate iframe that inherits none of the redesign, so theming the page does nothing for a modal it opens or a plugin in a slot; each one needs its own wrapper. Also: every icon from `@wix/wix-ui-icons-common/lazy`, and `--wds-*` tokens or `skin`/`size` props rather than hardcoded colours, font sizes or inline `style` ([BUSINESS_MANAGER_TOKENS.md](references/BUSINESS_MANAGER_TOKENS.md) — note the theme rebases the `SP*` spacing unit from 6px to 4px). `tsc`, `wix build` and `wix preview` all pass on an unthemed surface — only looking at it catches this. See [BUSINESS_MANAGER_THEME.md](references/BUSINESS_MANAGER_THEME.md).
48
- - [ ] **Step 4c (dashboard page UI only):** Re-opened and read the page file(s) just written — not recalled intent — and confirmed against the actual code: no `SummaryBar` unless the request asked for one, a routed drill-in (`navigateToEntityPage`) for every row and no `SidePanel` used as one, every declared filter name also appearing inside `fetchData`, and — for Cases A/B/D — the entry file both passes and guards `location`. See [UX Completeness Self-Audit](#step-4c-ux-completeness-self-audit).
48
+ - [ ] **Step 4c (dashboard page UI only):** Re-opened and read the page file(s) just written — not recalled intent — and confirmed against the actual code: no `SummaryBar` unless the request asked for one, a routed drill-in (`navigateToEntityPage`) for every row and no `SidePanel` used as one, every declared filter name also appearing inside `fetchData`, and — for every template with a router — the entry file both passes and guards `location`. See [UX Completeness Self-Audit](#step-4c-ux-completeness-self-audit).
49
49
  - [ ] **Step 5:** Ran validation (see [Validation](#validation))
50
50
  - [ ] Dependencies installed
51
51
  - [ ] TypeScript compiled
@@ -89,7 +89,7 @@ Helps build extensions for Wix CLI applications. Covers all extension types: das
89
89
 
90
90
  ## Component Selection Order
91
91
 
92
- **For the page shell, provider nesting, and routing — the part every dashboard page needs — start from [DRAFT_TEMPLATE.md](references/dashboard-page/DRAFT_TEMPLATE.md), not this section.** It has four verified cases (collection only, collection+entity, settings only, all three); pick the one the request matches and adapt it. What follows here is for individual UI elements the matching case's files don't already show — a filter type, a column renderer, a component the request needs that isn't in the skeleton.
92
+ **For the page shell, provider nesting, and routing — the part every dashboard page needs — start from [DRAFT_TEMPLATE.md](references/dashboard-page/DRAFT_TEMPLATE.md), not this section.** It finds the page templates the installed `@wix/patterns` ships (read-only list, list with create/edit, settings only, and a CMS collection on its schema); copy the one the request matches and adapt it. What follows here is for individual UI elements the template doesn't already show — a filter type, a column renderer, a component the request needs that isn't in it.
93
93
 
94
94
  Dashboard pages at Wix are built from two libraries. For **every** UI element not already covered by the template, resolve in this order and stop at the first hit. Never skip a step, and never decide a component is missing from memory — check.
95
95
 
@@ -106,8 +106,10 @@ up rather than assembling it from WDS parts.
106
106
  that answer as guides inside the installed package, with every component name in them checked
107
107
  against the real package at build time. Walk them: [The Discovery Chain](references/WIX_PATTERNS_DOCS.md#the-discovery-chain).
108
108
 
109
- One thing this skill owns rather than the guides: a **worked router skeleton** for multi-page
110
- extensions (Cases B/D), in [DRAFT_TEMPLATE_ROUTER.md](references/dashboard-page/DRAFT_TEMPLATE_ROUTER.md).
109
+ Whole pages are the package's too: the router wiring for a multi-page extension, and a
110
+ **collection whose fields the CMS owns** — `useCmsSchemaSource` from `@wix/patterns-cms`, with
111
+ `@wix/patterns/schema`, where the schema supplies fetch, filters, columns and the form — both ship
112
+ as page templates. See [DRAFT_TEMPLATE.md](references/dashboard-page/DRAFT_TEMPLATE.md).
111
113
 
112
114
  The short version — probe `<pkgRoot>/dist/docs/index.json` first (`grep`/`python3`, not a
113
115
  whole-file `Read`), then the guides it lists. From
@@ -213,10 +215,7 @@ Use a Dashboard Modal for dialogs that neither write nor display a listed record
213
215
  | Business Manager theme — the wrapper every dashboard page, modal and plugin needs | [BUSINESS_MANAGER_THEME.md](references/BUSINESS_MANAGER_THEME.md) |
214
216
  | Business Manager tokens — `--wds-*` replacements, the rebased spacing unit, per-component adjustments | [BUSINESS_MANAGER_TOKENS.md](references/BUSINESS_MANAGER_TOKENS.md) |
215
217
  | Dashboard UX Success Model (what a good dashboard contains) | [UX_SUCCESS_MODEL.md](references/dashboard-page/UX_SUCCESS_MODEL.md) |
216
- | Draft template — start here for any dashboard page (Cases A/B/C/D) | [DRAFT_TEMPLATE.md](references/dashboard-page/DRAFT_TEMPLATE.md) |
217
- | Draft template — collection page for Cases A/B/D | [DRAFT_TEMPLATE_COLLECTION.md](references/dashboard-page/DRAFT_TEMPLATE_COLLECTION.md) |
218
- | Draft template — router wiring for Cases A/B/D, incl. the read-only detail route | [DRAFT_TEMPLATE_ROUTER.md](references/dashboard-page/DRAFT_TEMPLATE_ROUTER.md) |
219
- | Draft template — settings page for Cases C/D | [DRAFT_TEMPLATE_SETTINGS.md](references/dashboard-page/DRAFT_TEMPLATE_SETTINGS.md) |
218
+ | Draft template — start here for any dashboard page: finding, copying and adapting the package's page templates | [DRAFT_TEMPLATE.md](references/dashboard-page/DRAFT_TEMPLATE.md) |
220
219
  | The state object `useTableCollection()` returns | [TABLE_STATE.md](references/dashboard-page/TABLE_STATE.md) |
221
220
  | Finding the SDK method and field names behind a page | [DATA_SOURCES.md](references/dashboard-page/DATA_SOURCES.md) |
222
221
  | Filter paths, WQL operators and cursor paging | [QUERY_AND_PAGING.md](references/dashboard-page/QUERY_AND_PAGING.md) |
@@ -419,10 +418,10 @@ Before moving to Step 5, re-open every page file you just wrote and check the ac
419
418
  - [ ] **Every row opens a page**: a `navigateToEntityPage` call literally appears in `onRowClick` — unless the prompt is explicitly a report or export-only view. A `SidePanel` in a collection page file is the defect this replaces; a display-only page routes to a read-only detail page, it does not fall back to a panel. `grep -n "<SidePanel" <page files>` should return nothing — match the JSX tag, not the bare word, or the templates' own "never a SidePanel" comments fail the check and invite someone to "fix" correct code.
420
419
  - [ ] Every filter name declared in the toolbar also appears inside `fetchData`'s query construction — grep for the name in both places if unsure.
421
420
  - [ ] The table wires `errorState` — without it a failed query is indistinguishable from a slow one, and the page you just shipped cannot tell you which it is.
422
- - [ ] **Case B/D only — the entry file both passes and guards `location`.** `PatternsReactRouter` throws at open when `location` is missing *or* still `undefined` on the first render, and `tsc`, `wix build` and even a green build all pass regardless. Both halves are required — the `location={location}` prop **and** the `location ? … : null` guard around it, since `observeState` has not fired yet on the first render. Grep the entry file rather than trusting recall:
421
+ - [ ] **Routed templates only (all but the settings one) — the entry file both passes and guards `location`.** `PatternsReactRouter` throws at open when `location` is missing *or* still `undefined` on the first render, and `tsc`, `wix build` and even a green build all pass regardless. Both halves are required — the `location={location}` prop **and** the `location ? … : null` guard around it, since `observeState` has not fired yet on the first render. Grep the entry file rather than trusting recall:
423
422
 
424
423
  ```bash
425
- grep -n "observeState\|location={location}\|location ?" src/extensions/dashboard/pages/<page>/<page>.tsx
424
+ grep -n "observeState\|location={location}\|location ?" src/extensions/dashboard/pages/<page>/<page>.tsx # the file the builder's `component` points at
426
425
  ```
427
426
 
428
427
  Three hits is correct. A missing guard is the failure mode that has actually shipped: a measured run produced a page whose plumbing looked present and still crashed on open, while a re-run of the same prompt produced a working one — so this is intermittent, and re-running is not a check.
@@ -71,8 +71,7 @@ A prop on a component still wins over the provider, so a one-off `size="medium"`
71
71
 
72
72
  | Surface | File | Section |
73
73
  | --- | --- | --- |
74
- | Page, Cases A/B/D (router-wired) | `{feature}.tsx` | [DRAFT_TEMPLATE_ROUTER.md § 1](dashboard-page/DRAFT_TEMPLATE_ROUTER.md#1-entry--location-is-manual-in-a-wix-cli-app-and-only-because-the-router-needs-it) |
75
- | Page, Case C (settings only) | `{feature}.tsx` | [DRAFT_TEMPLATE.md § 1](dashboard-page/DRAFT_TEMPLATE.md#1-entry--case-c-only-router-free-no-location-plumbing) |
74
+ | Page, from any `@wix/patterns` page template | `{feature}.tsx`, holding the template's `page.tsx` | [DRAFT_TEMPLATE.md § 3](dashboard-page/DRAFT_TEMPLATE.md#3-copy-it-into-the-extension) |
76
75
  | Page with dynamic parameters | `withProviders.tsx` | [DYNAMIC_PARAMETERS.md](dashboard-page/DYNAMIC_PARAMETERS.md#provider-wrapper-implementation) |
77
76
  | Modal | the generated `<modal>.tsx` | [DASHBOARD_MODAL.md](DASHBOARD_MODAL.md#theme) |
78
77
  | Plugin | the generated plugin component | [DASHBOARD_PLUGIN.md](DASHBOARD_PLUGIN.md#theme) |
@@ -77,5 +77,5 @@ Two rows from the design-side migration note have **no API to land on at 1.330.0
77
77
 
78
78
  **Several adjustments the providers already make for you** — don't redo them by hand: a nested icon button is Dark / Tertiary from the wrapper's `IconButton` default, and `WixDesignSystemIconThemeProvider` already supplies the Accordion caret. `Accordion`, `CloseButton`, `Tooltip`, `Popover`, `Thumbnail`, `SidePanel` and ~20 more are all legal keys of `WixDesignSystemDefaultProps`, so anything you find yourself repeating per call site probably belongs in `BUSINESS_MANAGER_DEFAULTS` instead.
79
79
 
80
- Two genuine author decisions remain: a **standalone** text button uses the standard (blue) skin while one beside a primary or secondary button uses Dark; and empty states are **typography only**, no legacy illustrations (on a collection page the empty state comes from the patterns shell — [DRAFT_TEMPLATE_COLLECTION.md](dashboard-page/DRAFT_TEMPLATE_COLLECTION.md)).
80
+ Two genuine author decisions remain: a **standalone** text button uses the standard (blue) skin while one beside a primary or secondary button uses Dark; and empty states are **typography only**, no legacy illustrations (on a collection page the empty state comes from the patterns shell — the page template's, [DRAFT_TEMPLATE.md](dashboard-page/DRAFT_TEMPLATE.md)).
81
81
 
@@ -29,13 +29,13 @@ wix generate --params '{"extensionType":"DASHBOARD_PAGE","title":"<title>","rout
29
29
  | `title` | Display name shown in the dashboard sidebar. |
30
30
  | `route` | URL path segment (lowercase alphanumeric + hyphens). The page is served at `/dashboard/<route>`. The scaffold param is `route`; the builder file's runtime field is `routePath`. |
31
31
 
32
- The CLI generates the folder, `page.tsx`, the builder file, the UUID, and the `src/extensions.ts` registration. After scaffolding, implement the page UI in the generated `page.tsx`.
32
+ The CLI generates the folder, the page's component file (`<page>.tsx`, the builder's `component`), the builder file, the UUID and the `src/extensions.ts` registration.
33
33
 
34
- **Never reuse `<route>` as a path prefix inside this page's `PatternsReactRouter`** — its location is already page-scoped, so a page scaffolded `route: "shifts"` still routes from `path="/"`, and `path="/shifts"` silently never matches. Page-relative paths: `<pkgRoot>/dist/docs/Collection to Entity Flow.md`.
34
+ **After scaffolding, check `routePath`:** use `support-tickets`, not `/support-tickets`; a leading slash fails registration even if the build passes. Internal router paths stay `/`, `/:id`, `/new`, never prefixed with `<route>` — see `<pkgRoot>/dist/docs/Collection to Entity Flow.md`.
35
35
 
36
- **Before writing that UI:** pick the matching case in [DRAFT_TEMPLATE.md](dashboard-page/DRAFT_TEMPLATE.md#which-case-matches-the-request) and copy its files in over the generated stub — don't compose the shell from scratch.
36
+ **Before writing that UI:** copy in the installed `@wix/patterns` page template that matches, per [DRAFT_TEMPLATE.md](dashboard-page/DRAFT_TEMPLATE.md) — don't compose the shell from scratch.
37
37
 
38
- **Then, for whatever the template case doesn't already cover:** resolve the package root and probe `<pkgRoot>/dist/docs/index.json` once with `grep`/`python3` — not a whole-file `Read`, which truncates it silently — per [Prerequisites](WIX_PATTERNS_DOCS.md#prerequisites). That index carries each symbol's `importPath`, `examples` and `bundle`, which is what decides whether you need to open anything else at all. Each Bash call is a fresh shell — re-set the path variable in every call.
38
+ **Then, for whatever the template doesn't cover:** probe `<pkgRoot>/dist/docs/index.json` once with `grep`/`python3` — never a whole-file `Read`, which truncates silently ([Prerequisites](WIX_PATTERNS_DOCS.md#prerequisites)). Its `importPath`, `examples` and `bundle` decide whether you open anything else. Each Bash call is a fresh shell — re-set the path variable every time.
39
39
 
40
40
  ## Capabilities
41
41
 
@@ -90,15 +90,15 @@ When building a dashboard page to configure an embedded script, see [Dynamic Par
90
90
 
91
91
  ## Examples
92
92
 
93
- Each starts from a case in [DRAFT_TEMPLATE.md](dashboard-page/DRAFT_TEMPLATE.md) — copy that case's files, then adapt. Only what differs per request is listed below; the wiring lives solely in the template.
93
+ Each starts from one of the package's page templates, found through [DRAFT_TEMPLATE.md](dashboard-page/DRAFT_TEMPLATE.md) — copy its files, then adapt. Only what differs per request is listed below; the wiring lives solely in the template.
94
94
 
95
- | Request | Case | Adapt |
95
+ | Request | Template | Adapt |
96
96
  | --- | --- | --- |
97
- | "Dashboard page to manage blog posts" | B | Columns for the post fields named; search, row actions and empty state from the collection's own APIs; add/edit navigate to the `EntityPage`; `{feature}-api.ts` calls `@wix/blog` |
98
- | "Settings page for notification preferences" | C | `SettingsPage` shell with a WDS field per preference (`FormField`, `Input`, `ToggleSwitch`); save confirms with `dashboard.showToast()`, and `dashboard.onBeforeUnload()` warns on unsaved changes — no collection, no table hook, no router |
99
- | "Admin panel for customer orders" | B | Filters, sorting and row actions from the collection APIs — **not** a hand-built WDS filter bar; status `Badge` is leaf UI in a cell; data source is `@wix/ecom`, never CMS (see [SDK-First Rule](../SKILL.md#sdk-first-rule-existing-wix-app-data-is-never-cms)); viewing or editing opens the `EntityPage`, and a Dashboard Modal appears only for the delete confirmation (see [Entity create and edit](../SKILL.md#entity-create-and-edit)) |
100
- | "Settings page for the coupon popup embedded script" | C | Fields for headline, coupon code, min cart value, enable toggle; swap `fetch`/`onSave` for `embeddedScripts.getEmbeddedScript()`/`embedScript()`, string-converted both ways (see [Dynamic Parameters](dashboard-page/DYNAMIC_PARAMETERS.md)); use `withProviders` in place of the template's plain provider |
101
- | "Admin page to manage fees, with an app settings section" | D | One extension, not several — fee fields/calls into all four route components. Skipping the router's `location` plumbing here passes `tsc`/`wix build` silently and fails only in a browser |
97
+ | "Dashboard page to manage blog posts" | Collection and Entity | Columns for the post fields named; search, row actions and empty state from the collection's own APIs; add/edit navigate to the `EntityPage`; `{feature}-api.ts` calls `@wix/blog` |
98
+ | "Settings page for notification preferences" | Settings Page | `SettingsPage` shell with a WDS field per preference (`FormField`, `Input`, `ToggleSwitch`); save confirms with `dashboard.showToast()`, and `dashboard.onBeforeUnload()` warns on unsaved changes — no collection, no table hook, no router |
99
+ | "Admin panel for customer orders" | Collection and Entity | Filters, sorting and row actions from the collection APIs — **not** a hand-built WDS filter bar; status `Badge` is leaf UI in a cell; data source is `@wix/ecom`, never CMS (see [SDK-First Rule](../SKILL.md#sdk-first-rule-existing-wix-app-data-is-never-cms)); viewing or editing opens the `EntityPage`, and a Dashboard Modal appears only for the delete confirmation (see [Entity create and edit](../SKILL.md#entity-create-and-edit)) |
100
+ | "Settings page for the coupon popup embedded script" | Settings Page | Fields for headline, coupon code, min cart value, enable toggle; swap `fetch`/`onSave` for `embeddedScripts.getEmbeddedScript()`/`embedScript()`, string-converted both ways (see [Dynamic Parameters](dashboard-page/DYNAMIC_PARAMETERS.md)); use `withProviders` in place of the template's plain provider |
101
+ | "Admin page to manage fees, with an app settings section" | Collection and Entity, plus the Settings Page's settings page as one more route | One extension, not several — fee fields/calls into every route component. Skipping the router's `location` plumbing here passes `tsc`/`wix build` silently and fails only in a browser |
102
102
 
103
103
 
104
104
  ## API Spec Support
@@ -12,7 +12,7 @@ Then check the install, two ways: `ls <pkgRoot>/dist/dts-bundle/index.json`, and
12
12
 
13
13
  **If either fails, stop and upgrade `@wix/patterns`.** "Which component serves this need" now lives in the package, so proceeding means guessing names; do not hunt elsewhere in `node_modules` for a substitute. Everything below degrades by version rather than breaking, so probe rather than version-check.
14
14
 
15
- **Patterns API facts come from three published trees only:** `dist/docs/` (pages), `dist/dts-bundle/` (types), `dist/examples/` (worked calls). Never take a component, prop or type from `src/`, `dist/esm/` or `dist/cjs/`, or a deep path a bundle mentions — internals change without notice. `dist/types/` is one narrow exception, used only where step 5 says: `package.json` points `types` at it, so it is what `tsc` enforces. Within those trees, `grep`/`sed` for one declaration usually beats a whole-file read.
15
+ **Patterns API facts come from four published trees only:** `dist/docs/` (pages), `dist/dts-bundle/` (types), `dist/examples/` (worked calls), `dist/templates/` (whole pages). Never take a component, prop or type from `src/`, `dist/esm/` or `dist/cjs/`, or a deep path a bundle mentions — internals change without notice. `dist/types/` is one narrow exception, used only where step 5 says: `package.json` points `types` at it, so it is what `tsc` enforces.
16
16
 
17
17
  ## The Discovery Chain
18
18
 
@@ -38,6 +38,7 @@ Start here rather than the bundle index: it carries every documented name *and*
38
38
 
39
39
  | Building | Guide |
40
40
  | --- | --- |
41
+ | A whole page | `dist/docs/Page Templates.md`, via [DRAFT_TEMPLATE.md](dashboard-page/DRAFT_TEMPLATE.md) |
41
42
  | Anything collection-shaped — tables, filters, search, aggregates, row and bulk actions, empty states | `dist/docs/Collection Toolkit.md` |
42
43
  | The path from a listed row to one record, and its form | `dist/docs/Collection to Entity Flow.md` |
43
44
 
@@ -53,7 +54,7 @@ Each guide's index entry carries `relatedComponents`; every name in it resolves,
53
54
  | What props, and which are optional? | the **example** first; then the **`.d.ts`** *or* the doc — never both | `bundle` present: `dist/dts-bundle/<bundle>` · absent: `dist/docs/<file>` |
54
55
  | A setup requirement or gotcha? | the **doc's** prose | `dist/docs/<file>` |
55
56
 
56
- Prefix the index's bare values with their tree — `file` → `dist/docs/`, `examples` → `dist/examples/`, `bundle` → `dist/dts-bundle/` — and never rebuild a path from a symbol name.
57
+ Prefix the index's bare values with their tree — `file` → `dist/docs/`, `examples` → `dist/examples/`, `bundle` → `dist/dts-bundle/`, `templateFiles` → `dist/templates/` — and never rebuild a path from a symbol name.
57
58
 
58
59
  **Those two middle rows are one question in practice.** You ask how to call it, start writing, and the question turns prop-shaped mid-call — where the second row sends you to a `.d.ts` that may be stubbed (step 5). Take the props off the example first; go to the `.d.ts` only for what it cannot show — optionality, union members, an exact callback signature.
59
60
 
@@ -1,98 +1,95 @@
1
1
  # Draft Template — the starting point for every dashboard page
2
2
 
3
- **Start here for any Dashboard Page request, before writing a shell, provider, or router from scratch.** Pick the case below, copy its files, rename, and adapt fields/API calls/data source. Only leave this file when the request needs something no case shows, and then go to [WIX_PATTERNS_DOCS.md](../WIX_PATTERNS_DOCS.md) first — before any lookup inside `node_modules/@wix/patterns`, `index.json` included. It routes you to the package's own `Collection Toolkit.md` guide, a component doc or an example; [TABLE_STATE.md](TABLE_STATE.md) and MCP lookups come after it.
3
+ **Start here for any Dashboard Page request, before writing a shell, provider, or router from scratch.** The templates themselves ship inside the installed `@wix/patterns`: whole, working pages that the package type-checks against its own release, so they never drift from the version your code compiles against. This file says how to find one, where its files go in a Wix CLI app, and what this skill adds on top. Which template fits, and the wiring each one must keep, is the package's answer — read it there.
4
4
 
5
- Every snippet below was copied from the installed `dist/docs/*.md` and `dist/dts-bundle/*.d.ts`, not from memory — confirm props against your own installed version before deviating.
5
+ Go through [WIX_PATTERNS_DOCS.md § Prerequisites](../WIX_PATTERNS_DOCS.md#prerequisites) first for `<pkgRoot>`; every path below is relative to it.
6
6
 
7
- ## Which case matches the request?
7
+ ## 1. Find the templates
8
8
 
9
- | The request needs… | Case | Router? |
10
- | --- | --- | --- |
11
- | A list/report whose rows are read-only — no create/edit form | **A — Collection + read-only detail** | Yes |
12
- | A list **and** create/edit for each record (no separate app-settings area) | **B — Collection + Entity** | Yes |
13
- | Only app-wide settings/config — no list at all | **C — Settings only** | No |
14
- | A list, create/edit, **and** an app-settings area, all in one extension | **D — Collection + Entity + Settings** | Yes |
9
+ Templates are docs-index entries with `category: "Templates"`, and each lists every file of its page in `templateFiles` (relative to `dist/templates/`). One probe lists them all with the guide that chooses between them:
15
10
 
16
- Don't default to D because it's the most complete — [Step 4c's checklist](../../SKILL.md#step-4c-ux-completeness-self-audit) doesn't ask for a settings or entity page unless the request needs one. If unsure between B and D, re-read the prompt for "settings," "configure," "preferences" — their absence means B.
17
-
18
- ## Then: what `fetchData` calls
19
-
20
- Case tells you how many pages. This tells you only what `fetchData` calls — the page itself is
21
- [DRAFT_TEMPLATE_COLLECTION.md](DRAFT_TEMPLATE_COLLECTION.md) either way:
11
+ ```bash
12
+ python3 -c "
13
+ import json; i = json.load(open('<pkgRoot>/dist/docs/index.json'))
14
+ for k, e in i.items():
15
+ if e.get('category') == 'Templates' or e.get('relatedTemplates'):
16
+ print(k, '|', e.get('templateFiles') or e.get('relatedTemplates'), '|', e.get('summary', '')[:200])
17
+ "
18
+ ```
22
19
 
23
- | The rows come from | `fetchData` calls |
24
- | --- | --- |
25
- | A vertical SDK (`@wix/bookings`, `@wix/ecom`, …) or any API you call yourself | that SDK's own query method — [DATA_SOURCES.md](DATA_SOURCES.md) |
26
- | A **CMS collection** — one your Data Collection extension ships, or an existing site collection | `@wix/data` `items.query()` — [WIX_DATA.md](../data-collection/WIX_DATA.md) |
20
+ **No `Templates` entries means the install predates them — upgrade `@wix/patterns`.** Do not rebuild a page from memory or from an older copy of this skill instead: the templates exist so the shell, provider nesting and router wiring come from the package.
27
21
 
28
- A CMS collection is not a different template, just a different call inside the same one: you write
29
- `fetchData`, the filters and the columns for both.
22
+ ## 2. Choose, then read the chosen template's page
30
23
 
31
- **Cases B and D both need a router** — their entry file, app shell, and entity page are in [DRAFT_TEMPLATE_ROUTER.md](DRAFT_TEMPLATE_ROUTER.md). The collection and settings files are shared by every case that uses them, B and D included; the router file links back rather than repeating them.
24
+ 1. `Read <pkgRoot>/dist/docs/Page Templates.md` — which pages the request needs (read-only list, list with create/edit, settings only, or list with create/edit and settings) and where the rows come from (your own fetch, or a CMS collection).
25
+ 2. `Read` the chosen template's page (`dist/docs/<entry file>`) — its files, and the wiring to keep. Most of that wiring passes `tsc` and `wix build` when wrong and fails only in the browser, which is why [Step 5's Preview](../../SKILL.md#validation) is not optional for a routed page.
32
26
 
33
- ## File layout
27
+ Take the smallest template that covers what the request names. A settings area or an edit form nobody asked for is not "more complete" — [Step 4c's checklist](../../SKILL.md#step-4c-ux-completeness-self-audit) does not ask for one.
34
28
 
35
- ```
36
- src/extensions/dashboard/pages/{feature}/
37
- {feature}.extension.ts # single wix generate scaffold — always exactly one route registered here
38
- {feature}.tsx # entry — Case C: Section 1 below. Cases A/B/D: DRAFT_TEMPLATE_ROUTER.md
39
- {Feature}App.tsx # Case A/B/D only — see DRAFT_TEMPLATE_ROUTER.md
40
- {Feature}CollectionPage.tsx # Case A, B, D — see DRAFT_TEMPLATE_COLLECTION.md
41
- {Feature}DetailPage.tsx # Case A only — read-only detail route, DRAFT_TEMPLATE_ROUTER.md §4
42
- {Feature}EntityPage.tsx # Case B, D only — see DRAFT_TEMPLATE_ROUTER.md
43
- {Feature}SettingsPage.tsx # Case C, D only — see DRAFT_TEMPLATE_SETTINGS.md
44
- {feature}-api.ts # fetch/save calls — keep these out of the components
45
- ```
29
+ ## 3. Copy it into the extension
46
30
 
47
- Scaffold with a single call regardless of case — this is always one extension:
31
+ Scaffold once — every template is one extension, however many routes it has:
48
32
 
49
33
  ```bash
50
34
  wix generate --params '{"extensionType":"DASHBOARD_PAGE","title":"<title>","route":"<route>"}'
51
35
  ```
52
36
 
53
- ## 1. Entry — Case C only (router-free, no location plumbing)
37
+ `route` takes no leading slash — `support-tickets`, not `/support-tickets`. See [Dashboard Page → Scaffold](../DASHBOARD_PAGE.md#scaffold).
38
+
39
+ Then copy **every** file in `templateFiles` into the generated folder (`src/extensions/dashboard/pages/<feature>/`). They import one another by relative path, so keep their names relative to each other; one file on its own does not compile.
40
+
41
+ - The template's `page.tsx` is the entry file, and the CLI generated none by that name: put its contents into the component file the builder's `component` path points at (e.g. `employee-shifts.tsx`), and add no separate `page.tsx`, which nothing loads. Leave the builder file and the `src/extensions.ts` registration as the CLI wrote them.
42
+ - Rename `Items` / `Item` to your feature and entity, in file names and identifiers alike.
43
+ - Route paths stay page-relative (`/`, `/:id`, `/new`) — never prefix them with `<route>` ([DASHBOARD_PAGE.md](../DASHBOARD_PAGE.md)).
44
+
45
+ - **Theme the entry file.** In it, replace the template's `WixDesignSystemProvider` and its `@wix/design-system/styles.global.css` import with the app's `BusinessManagerTheme` — written once per app ([BUSINESS_MANAGER_THEME.md § 2](../BUSINESS_MANAGER_THEME.md#2-the-wrapper--write-this-file-once-per-app)), kept in the entry file so it sits above `WixPatternsProvider`. **Business Manager passes none of the redesign through the extension's iframe**, so the bare provider renders the pre-redesign look while `tsc`, `wix build` and `wix preview` all pass.
46
+
47
+ ## 4. Replace the data file
54
48
 
55
- Case C has one page and no rows to open, so it needs no router and no manual `location` wiring. **Cases A, B and D all route** — a row opens a page of its own in every one of them — so their entry file is [DRAFT_TEMPLATE_ROUTER.md](DRAFT_TEMPLATE_ROUTER.md) §1:
49
+ Each template's data file (`items-api.ts`, `settings-api.ts`) is an in-memory stand-in with the signatures the pages call. Replace the bodies, keep the signatures:
56
50
 
57
- ```tsx
58
- // {feature}.tsx — Case C
59
- import type { FC } from 'react';
60
- import { withDashboard } from '@wix/patterns';
61
- import { WixPatternsProvider } from '@wix/patterns/provider';
62
- import { BusinessManagerTheme } from '../../BusinessManagerTheme'; // BUSINESS_MANAGER_THEME.md § 2
63
- import { {Feature}SettingsPage } from './{Feature}SettingsPage'; // DRAFT_TEMPLATE_SETTINGS.md
51
+ - **Whose data it is decides the call.** Data an existing Wix app owns comes from that app's SDK, never a CMS collection ([SDK-First Rule](../../SKILL.md#sdk-first-rule-existing-wix-app-data-is-never-cms)). The method and field names: [DATA_SOURCES.md](DATA_SOURCES.md). Filter paths, operators and cursor paging: [QUERY_AND_PAGING.md](QUERY_AND_PAGING.md).
52
+ - **A CMS collection uses the CMS template, which has no data file** — its `collection.ts` takes the full scoped id, `<app-namespace>/<idSuffix>` for a collection your extension ships ([DATA_COLLECTION.md](../DATA_COLLECTION.md)). The collection does not exist until the app is released ([LIFECYCLE.md](../data-collection/LIFECYCLE.md#the-extension-does-not-create-the-collection)).
53
+ - **On a CMS collection, the prompt's fields are the collection's fields** — every one a column, none hand-written. For a collection your extension ships, create exactly those fields; an existing site collection shows all its fields, and the user hides the rest in the column picker. A stated column list decides the columns only on the hand-wired path.
64
54
 
65
- const Page: FC = () => (
66
- <BusinessManagerTheme>
67
- <WixPatternsProvider>
68
- <{Feature}SettingsPage />
69
- </WixPatternsProvider>
70
- </BusinessManagerTheme>
71
- );
55
+ ### Installing the CMS pair
72
56
 
73
- export default withDashboard(Page);
57
+ `@wix/patterns-cms` pins one exact `@wix/patterns` version, and the template's page says why a second copy breaks the page silently.
58
+
59
+ **Already in `package.json`** (some app templates ship both): don't install it again — go straight to the check. **Not there:** install it, then align `@wix/patterns` to its pin:
60
+
61
+ ```bash
62
+ npm install @wix/patterns-cms
63
+ npm install @wix/patterns@$(node -p "require('@wix/patterns-cms/package.json').dependencies['@wix/patterns']")
74
64
  ```
75
65
 
76
- `BusinessManagerTheme` is the app's own wrapper around the design-system providers, the `odeditor` theme stylesheet and the Business Manager default props — [BUSINESS_MANAGER_THEME.md § 2](../BUSINESS_MANAGER_THEME.md#2-the-wrapper--write-this-file-once-per-app) has the file to write once per app. **Business Manager passes none of the redesign through the extension's iframe**, so a bare `WixDesignSystemProvider` here renders the pre-redesign look while `tsc`, `wix build` and `wix preview` all pass. `WixPatternsProvider` nests inside it, never above.
66
+ **Either way, confirm a single copy:**
77
67
 
78
- Cases A, B and D differ — they need `location` supplied manually for `PatternsReactRouter`. See [DRAFT_TEMPLATE_ROUTER.md](DRAFT_TEMPLATE_ROUTER.md) rather than adding that plumbing here; it's dead code without a router underneath it.
68
+ ```bash
69
+ npm dedupe
70
+ find node_modules -path '*@wix/patterns/package.json' -not -path '*/dist/*'
71
+ ```
79
72
 
80
- ## 2. Collection page — Case A, B, D
73
+ More than one line is a bug that `tsc` and `wix build` both pass. Declared ranges produce it when `@wix/patterns` ships ahead of `@wix/patterns-cms`, or a lockfile holds an older one. Run the second `npm install` line above, then the check again.
81
74
 
82
- In [DRAFT_TEMPLATE_COLLECTION.md](DRAFT_TEMPLATE_COLLECTION.md) — `useTableCollection`, a working
83
- filter, the four placeholder states, and a row that opens. Shared by Cases A, B and D. No
84
- `SummaryBar` unless the request asked for one, and the drill-in is always a route — never a side
85
- panel.
75
+ ### Turning `query.search` into a query
76
+
77
+ On the hand-wired path the data file turns one search term into a filter over *several* fields — an OR. **This is where search ships broken**: it renders, it reaches the query, and still returns every row. (The CMS template's source does this itself.)
78
+
79
+ There is no shared free-text operator; the shape differs per endpoint. Read its *Supported Filters* page ([QUERY_AND_PAGING.md](QUERY_AND_PAGING.md#the-filterable-fields-are-a-closed-list-published-per-endpoint)), then `$or` one clause per identity field it lists. Never route the term to a single field by its shape — a measured run shipped this, and one branch is always dead:
80
+
81
+ ```ts
82
+ query = term.includes('@')
83
+ ? query.startsWith('loginEmail', term)
84
+ : query.startsWith('contact.firstName', term); // a surname matches nothing, ever
85
+ ```
86
86
 
87
- ## 3. Settings page — Case C, D
87
+ `startsWith` is prefix-only too — "Smith" never finds "John Smith" — so prefer the containment operator when the endpoint declares one, and say which you used in `noResultsState`.
88
88
 
89
- In [DRAFT_TEMPLATE_SETTINGS.md](DRAFT_TEMPLATE_SETTINGS.md) — `useSettingsPage` + `useForm`,
90
- with the same field-controller patterns as the entity page.
89
+ **Whatever the shape, prove it narrows.** Run one term you expect to hit a known row and one you expect to hit nothing, and check the row count changes for both.
91
90
 
92
- ## What to change vs. keep, per case
91
+ ## 5. What this skill adds to every template
93
92
 
94
- | Change per request | Keep as shown |
95
- | --- | --- |
96
- | Feature/entity names, fields, columns, API calls in `{feature}-api.ts` | Which case (A/B/C/D) — don't over-build D for a request that only named a list |
97
- | Detail fields, form field types, columns | The drill-in being a route, and the absence of a `SummaryBar` the request never asked for |
98
- | Real data source (SDK-first per [SDK-First Rule](../../SKILL.md#sdk-first-rule-existing-wix-app-data-is-never-cms)) vs CMS | B/D wiring (provider/router nesting, `parentPath`, `location`): [DRAFT_TEMPLATE_ROUTER.md](DRAFT_TEMPLATE_ROUTER.md) |
93
+ - **No `SummaryBar` unless the request asked for one** — a named total, count or "how many / how much" figure. The templates ship without one on purpose ([SKILL.md § Step 2](../../SKILL.md)).
94
+ - **The state is MobX.** Deriving anything from the collection state in your own component — a header figure, a badge — goes through `useSelector` ([TABLE_STATE.md](TABLE_STATE.md#reading-state-outside-the-table-it-is-mobx)).
95
+ - **Anything the template does not show** — another filter type, a column renderer, a component the request needs — goes through [The Discovery Chain](../WIX_PATTERNS_DOCS.md#the-discovery-chain). A template replaces composing the shell, never the lookup for a symbol it does not contain.
@@ -17,10 +17,12 @@ about tooling, not evidence about the collection.
17
17
 
18
18
  ## Reading the collection from a dashboard page
19
19
 
20
- A page over a CMS collection is an ordinary collection page: the template is
21
- [DRAFT_TEMPLATE_COLLECTION.md](../dashboard-page/DRAFT_TEMPLATE_COLLECTION.md), and its `fetchData`
22
- calls `@wix/data` `items.query()` — see [WIX_DATA.md](WIX_DATA.md) for the query, result and paging
23
- shapes. You write the filters and the columns yourself, exactly as for a vertical SDK.
20
+ A page over a CMS collection uses the package's **CMS Collection and Entity Template**, not the
21
+ hand-wired one — the CMS already owns the field list, so `useCmsSchemaSource` supplies fetch,
22
+ filters, columns and the entity form from it. See
23
+ [DRAFT_TEMPLATE.md](../dashboard-page/DRAFT_TEMPLATE.md). Wiring
24
+ `items.query()` and hand-written columns by hand still compiles, and quietly gives up everything the
25
+ schema would have provided.
24
26
 
25
27
  ## The site must have CMS, or nothing is created at all
26
28
 
@@ -1,184 +0,0 @@
1
- # Draft Template — Collection page (Cases A, B and D)
2
-
3
- > Pick your case in [DRAFT_TEMPLATE.md](DRAFT_TEMPLATE.md) first. This page is shared by A, B
4
- > and D.
5
-
6
- > **You write `fetchData`, the filters and the columns.** This holds whatever the rows are: for a
7
- > CMS collection `fetchData` calls `@wix/data` `items.query()`
8
- > ([WIX_DATA.md](../data-collection/WIX_DATA.md)) and nothing else on this page changes.
9
-
10
- ## Two rules this skeleton encodes, so read them before editing it
11
-
12
- **Every row opens a page of its own, never a panel.** `onRowClick` navigates —
13
- `navigateToEntityPage` to an `EntityPage` route when the record is editable, or to a read-only
14
- detail route when the page is display-only ([DRAFT_TEMPLATE_ROUTER.md § 4](DRAFT_TEMPLATE_ROUTER.md#4-read-only-detail-route--case-a)).
15
- A WDS `SidePanel` is not the drill-in: in Cairo it is the host for a page's own side panels — the
16
- fields card's "Manage fields", a table's column panel — and `example-bm` uses it as a row drill-in
17
- in exactly nothing. Every collection example there navigates.
18
-
19
- **No `SummaryBar` unless the request asked for one.** Not "unless the page seems to want one" —
20
- unless the prompt named a total, a count, or a "how many / how much" figure. It is absent from this
21
- skeleton on purpose; adding one uninvited pushes the rows down the page and puts a number on screen
22
- nobody has to be right about. When you do add one, wire it from
23
- [SKILL.md § Step 2](../../SKILL.md) — no `SummaryBar` unless the request asked for one.
24
-
25
- ## The collection page
26
-
27
- This is the file [Step 4c's UX Completeness Self-Audit](../../SKILL.md#step-4c-ux-completeness-self-audit)
28
- is about: a working filter, named filters, and a row that opens.
29
-
30
- ```tsx
31
- // {Feature}CollectionPage.tsx — Case A, B, D
32
- import type { FC } from 'react';
33
- import {
34
- CollectionEmptyState, CollectionErrorState, CollectionNoResultsState, CollectionSearch, CollectionToolbarFilters,
35
- MultiSelectCheckboxFilter, Table, stringsArrayFilter,
36
- useStaticListFilterCollection, useTableCollection,
37
- } from '@wix/patterns';
38
- import { CollectionPage } from '@wix/patterns/page';
39
- import { usePatternsNavigate } from '@wix/patterns/router';
40
- import { fetch{Feature}Page, type {Entity}Row } from './{feature}-api';
41
-
42
- const STATUS_LABELS: Record<string, string> = { ACTIVE: 'Active', ARCHIVED: 'Archived' };
43
- // Filter factories are module-level: one instance per page, not per render.
44
- const statusFilter = stringsArrayFilter<'ACTIVE' | 'ARCHIVED'>({
45
- // `name` is NOT the visible title — it feeds a11y legends, BI grouping and dataHooks.
46
- // The title comes from the label props in the JSX below; the package's own
47
- // `Collection Toolkit.md` guide has the prop model.
48
- name: 'Status',
49
- itemKey: (item) => item,
50
- itemName: (item) => STATUS_LABELS[item] ?? item,
51
- });
52
-
53
- export const {Feature}CollectionPage: FC = () => {
54
- const { navigateToEntityPage } = usePatternsNavigate<{Entity}Row>();
55
-
56
- const state = useTableCollection<{Entity}Row, { status: typeof statusFilter }>({
57
- queryName: '{feature}',
58
- paginationMode: 'cursor', // 'offset' if your API pages by offset
59
- itemKey: (item) => item.id,
60
- itemName: (item) => item.name,
61
- filters: { status: statusFilter },
62
- // Every declared filter AND the search box are read here — one that never
63
- // reaches the query renders fine and narrows nothing.
64
- fetchData: async (query) =>
65
- fetch{Feature}Page({
66
- limit: query.limit,
67
- cursor: query.cursor ?? undefined, // return `cursor: next || undefined` — key always
68
- // present, '' walks pages forever
69
- search: query.search,
70
- filters: { status: query.filters.status },
71
- }),
72
- // fetchTotal is for a requested aggregate, so it is commented out here. Uncommented, it
73
- // must RESOLVE A NUMBER from an endpoint that counts, and it OVERRIDES the fallback to the
74
- // loaded-rows count — so a wrong one reports 0 beside a full table while tsc stays green.
75
- // Read QUERY_AND_PAGING.md, "What fetchTotal is allowed to call", before uncommenting.
76
- // fetchTotal: async (query) => count{Feature}({ search: query.search, filters: query.filters }),
77
- fetchErrorMessage: ({ err }) => (err instanceof Error ? err.message : 'Failed to load {feature}'),
78
- });
79
-
80
- const statusOptions = useStaticListFilterCollection(statusFilter, ['ACTIVE', 'ARCHIVED']);
81
-
82
- // Nothing here reads `state` directly: the Table observes it internally. The moment you
83
- // DO derive a number from it in this component — a header figure, a badge, a requested
84
- // SummaryBar — it is MobX, and a plain read is computed once while the collection is still
85
- // empty and never recomputed. Wrap it in `useSelector` and select a primitive: TABLE_STATE.md.
86
-
87
- return (
88
- <CollectionPage>
89
- <CollectionPage.Header title={{ text: '{Page Title}' }} />
90
- <CollectionPage.Content>
91
- <Table
92
- state={state}
93
- // `search` defaults to ON, so omitting it ships a box that reaches no
94
- // query. Wire it AND read query.search above. If the API has no
95
- // free-text filter, resolve the term to one it does support.
96
- search={<CollectionSearch placeholder="Search {feature}" />}
97
- filters={
98
- <CollectionToolbarFilters>
99
- {/* BOTH label props, same string, and never accordionItemProps.title.
100
- The resolution model is the package's own `Collection Toolkit.md` guide. */}
101
- <MultiSelectCheckboxFilter
102
- filter={statusFilter} collection={statusOptions}
103
- toolbarItemProps={{ label: 'Status' }}
104
- accordionItemProps={{ label: 'Status' }}
105
- />
106
- </CollectionToolbarFilters>
107
- }
108
- // Three distinct messages. Without errorState a failed query looks
109
- // exactly like a slow one: skeletons, forever.
110
- emptyState={<CollectionEmptyState title="No {feature} yet" />}
111
- // Name what excluded the rows — a seeded default or a missing scope
112
- // looks exactly like "nothing matched", and only one is actionable.
113
- noResultsState={<CollectionNoResultsState title="No {feature} match" subtitle="…" />}
114
- // errorState is a RENDER FUNCTION: (err, { retry }) => ReactElement.
115
- errorState={(err, { retry }) => (
116
- <CollectionErrorState
117
- title="Couldn't load {feature}"
118
- subtitle={String(err)}
119
- action={{ text: 'Retry', onClick: retry }}
120
- />
121
- )}
122
- // The drill-in. Editable record → the EntityPage route (`/${item.id}`).
123
- // Display-only → the read-only route, DRAFT_TEMPLATE_ROUTER.md §4. Either way a
124
- // route, and passing `entity` lets the target title itself before its fetch lands.
125
- onRowClick={(item) => navigateToEntityPage({ path: `/${item.id}`, entity: item })}
126
- columns={[
127
- // One column per field the prompt names; verify each source field —
128
- // DATA_SOURCES.md, "A column must show what its header promises."
129
- { id: 'name', title: 'Name', render: (item) => item.name },
130
- ]}
131
- />
132
- </CollectionPage.Content>
133
- </CollectionPage>
134
- );
135
- };
136
- ```
137
-
138
- ## Turning `query.search` into a query
139
-
140
- `{feature}-api.ts` turns one term into a filter over *several* fields — an OR. **This is where
141
- search ships broken**: it renders, it reaches the query, and still returns every row, so nothing
142
- looks wrong until someone counts results.
143
-
144
- ### A CMS collection — `@wix/data`
145
-
146
- `or()` combines two filters; it is **not** a condition. Called on a query or filter holding no
147
- condition yet it contributes an empty `{}` branch, and `{} OR x` matches the whole collection. Seed
148
- the filter with the first field, `or()` the rest onto it, then `and()` it onto the query — which is
149
- also what keeps the facet filters applying to *every* branch:
150
-
151
- ```ts
152
- // SHORT_TEXT / LONG_TEXT fields only: `contains` on a number, date or
153
- // reference field is not a narrower match, it is no match.
154
- const searchFilter = (term: string, fields: string[]) =>
155
- fields.map((f) => items.filter().contains(f, term)).reduce((acc, f) => acc.or(f));
156
-
157
- let query = items.query(COLLECTION_ID).eq('status', status).limit(limit);
158
- const term = search?.trim();
159
- if (term) query = query.and(searchFilter(term, ['companyName', 'contactEmail']));
160
-
161
- // ❌ or() onto a query holding no condition — empty $or branch, every row matches
162
- items.query(ID).or(items.filter().contains('a', t)).or(items.filter().contains('b', t));
163
- // ❌ or() onto the query itself — `status` survives only on the first branch
164
- items.query(ID).eq('status', s).contains('a', t).or(items.filter().contains('b', t));
165
- ```
166
-
167
- ### A vertical SDK — `@wix/bookings`, `@wix/ecom`, …
168
-
169
- No shared free-text operator; the shape differs per endpoint. Read its *Supported Filters* page
170
- ([QUERY_AND_PAGING.md](QUERY_AND_PAGING.md#the-filterable-fields-are-a-closed-list-published-per-endpoint)),
171
- then `$or` one clause per identity field it lists. Never route the term to a single field by its
172
- shape — a measured run shipped this, and one branch is always dead:
173
-
174
- ```ts
175
- query = term.includes('@')
176
- ? query.startsWith('loginEmail', term)
177
- : query.startsWith('contact.firstName', term); // a surname matches nothing, ever
178
- ```
179
-
180
- `startsWith` is prefix-only too — "Smith" never finds "John Smith" — so prefer the containment
181
- operator when the endpoint declares one, and say which you used in `noResultsState`.
182
-
183
- **Whatever the shape, prove it narrows.** Run one term you expect to hit a known row and one you
184
- expect to hit nothing, and check the row count changes for both.
@@ -1,204 +0,0 @@
1
- # Draft Template — Cases A, B and D (router-wired)
2
-
3
- **Applies to [DRAFT_TEMPLATE.md](DRAFT_TEMPLATE.md)'s Cases A, B and D.** All three need `PatternsReactRouter`, because in all three a row opens a page of its own — a panel is not the drill-in ([UX_SUCCESS_MODEL.md](UX_SUCCESS_MODEL.md)). Case A's detail route is read-only (§4); B and D route to a full `EntityPage`. This file covers only what a router adds.
4
-
5
- Every snippet below is copied from the installed `dist/docs/*.md` this session read, not from memory.
6
-
7
- ## 1. Entry — `location` is manual in a Wix CLI app, and only because the router needs it
8
-
9
- `PatternsReactRouter` reads the page location through `container.usePageLocation()`, which only `withDashboard`'s `location` prop feeds — nothing supplies it automatically in a Wix CLI app:
10
-
11
- ```tsx
12
- // {feature}.tsx — Case A, B or D
13
- import { useEffect, useState, type FC } from 'react';
14
- import { dashboard } from '@wix/dashboard';
15
- import { BusinessManagerTheme } from '../../BusinessManagerTheme';
16
- import { {Feature}App } from './{Feature}App';
17
-
18
- type PageLocation = Parameters<Parameters<typeof dashboard.observeState>[0]>[1]['pageLocation'];
19
-
20
- const Page: FC = () => {
21
- const [location, setLocation] = useState<PageLocation | undefined>();
22
-
23
- useEffect(() => {
24
- const subscription = dashboard.observeState((_pageParams, environment) => {
25
- setLocation(environment.pageLocation);
26
- });
27
- return () => subscription.disconnect();
28
- }, []);
29
-
30
- return (
31
- <BusinessManagerTheme>
32
- {location ? <{Feature}App location={location} /> : null}
33
- </BusinessManagerTheme>
34
- );
35
- };
36
-
37
- export default Page;
38
- ```
39
-
40
- `BusinessManagerTheme` belongs here, not in `{Feature}App.tsx` — its default props must apply above `WixPatternsProvider`. Write it once per app: [BUSINESS_MANAGER_THEME.md](../BUSINESS_MANAGER_THEME.md#2-the-wrapper--write-this-file-once-per-app).
41
-
42
- **Skipping either the theme or `location` is a runtime-only failure.** Per `PatternsReactRouter`'s own docs: "Nothing catches this before runtime — type checking and bundling both pass, because neither renders the page." Only a browser catches a missing `location` or an unthemed page, which is why [Step 5's Preview](../../SKILL.md#validation) is not optional.
43
-
44
- ## 2. App shell — provider, router, routes
45
-
46
- ```tsx
47
- // {Feature}App.tsx — Case A, B or D
48
- import { withDashboard } from '@wix/patterns';
49
- import { WixPatternsProvider } from '@wix/patterns/provider';
50
- import { PatternsReactRouter, PatternsReactRoute } from '@wix/patterns/router';
51
- import { {Feature}CollectionPage } from './{Feature}CollectionPage';
52
- import { {Feature}EntityPage } from './{Feature}EntityPage';
53
- // Case D only:
54
- import { {Feature}SettingsPage } from './{Feature}SettingsPage';
55
-
56
- export const {Feature}App = withDashboard(() => (
57
- <WixPatternsProvider>
58
- <PatternsReactRouter>
59
- <PatternsReactRoute type="collection" path="/" element={<{Feature}CollectionPage />} />
60
- {/* Case B/D — editable record. Case A registers this ONE route instead, with
61
- {Feature}DetailPage as the element and no /new route (§4). Either way the
62
- type is `editEntity`: it wires back-navigation to the collection. */}
63
- <PatternsReactRoute type="editEntity" path="/:id" element={<{Feature}EntityPage />} />
64
- {/* Case B/D only — Case A has nothing to create: */}
65
- <PatternsReactRoute type="createEntity" path="/new" element={<{Feature}EntityPage />} />
66
- {/* Case D only: */}
67
- <PatternsReactRoute type="other" path="/settings" element={<{Feature}SettingsPage />} />
68
- </PatternsReactRouter>
69
- </WixPatternsProvider>
70
- ));
71
- ```
72
-
73
- `PatternsReactRoute`'s `type` is one of exactly `"collection" | "createEntity" | "editEntity" | "other"` — no dedicated settings value, so a settings route is `"other"`. Case B: delete the `/settings` route and the import above it — nothing else changes.
74
-
75
- `{Feature}CollectionPage` is [DRAFT_TEMPLATE_COLLECTION.md](DRAFT_TEMPLATE_COLLECTION.md)'s component, unchanged; `{Feature}SettingsPage` (Case D) is [DRAFT_TEMPLATE_SETTINGS.md](DRAFT_TEMPLATE_SETTINGS.md).
76
-
77
- ## 2b. The collection side of the handoff
78
-
79
- The router file above registers `/new`; something has to navigate there. All three of these live on
80
- the collection page, and `usePatternsNavigate` comes from **`@wix/patterns/router`** — not the root
81
- export, which is where it is easy to reach for it:
82
-
83
- ```tsx
84
- import { PrimaryPageButton } from '@wix/patterns';
85
- import { usePatternsNavigate } from '@wix/patterns/router';
86
-
87
- const { navigateToEntityPage } = usePatternsNavigate<{Entity}>();
88
- const openNew = () => navigateToEntityPage({ path: '/new' }); // no record yet — omit `entity`
89
-
90
- // CollectionPage.Header — primaryAction takes an ELEMENT, not a { text, onClick } config:
91
- <CollectionPage.Header
92
- title={{ text: '{Page Title}' }}
93
- primaryAction={<PrimaryPageButton text="Add {entity}" onClick={openNew} />}
94
- />
95
-
96
- // the empty state should offer the same action, and this one IS a config object:
97
- <CollectionEmptyState title="No {feature} yet" addNewCta={{ text: 'Add {entity}', onClick: openNew }} />
98
-
99
- // and a row opens the edit route:
100
- onRowClick={(item) => navigateToEntityPage({ path: `/${item.id}`, entity: item })}
101
- ```
102
-
103
- The two shapes differ on purpose — `primaryAction` is an element, `addNewCta` is `{ text, onClick }`.
104
- Guessing either costs a compile round.
105
-
106
- ## 3. Entity page — one component for both `/new` and `/:id`
107
-
108
- Full call details (both generics, what `onSave` receives, the `UseEntityPageParams` pick list) are in `<pkgRoot>/dist/docs/useEntityPage.md` — read it before filling in fields. The part specific to this router-based shape:
109
-
110
- ```tsx
111
- // {Feature}EntityPage.tsx — Case B or D (sketch — see dist/docs/useEntityPage.md for the full call)
112
- import { useParams } from 'react-router-dom'; // NOT @wix/patterns/router — PatternsReactRoute wraps react-router-dom's Route, but only exports PatternsReactRoute/PatternsReactRouter/usePatternsNavigate itself
113
- import { useEntityPage, EntityPage } from '@wix/patterns';
114
- import { useForm } from '@wix/patterns/form';
115
- import { fetch{Entity}, save{Entity}, type {Entity} } from './{feature}-api';
116
-
117
- // The form's shape is yours to declare, and need not match the saved entity type.
118
- type {Entity}FormFields = { name: string };
119
-
120
- export const {Feature}EntityPage = () => {
121
- const { id } = useParams<{ id?: string }>();
122
- const form = useForm<{Entity}FormFields>();
123
-
124
- const state = useEntityPage<{Entity}, {Entity}FormFields>({
125
- parentPath: '/', // NOT parentPageId — this shape always uses Patterns Router
126
- isNewEntity: () => !id, // a getter: the route can change (/new -> /:id after save) while this stays mounted
127
- form,
128
- fetch: () => (id ? fetch{Entity}(id).then((entity) => ({ entity })) : Promise.resolve({ entity: undefined })),
129
- onSave: ({ widgetsFormData }) =>
130
- save{Entity}({ id, ...form.getValues(), ...widgetsFormData }).then((updatedEntity) => ({ updatedEntity })),
131
- });
132
-
133
- return (
134
- <EntityPage state={state}>
135
- <EntityPage.Header title={{ text: id ? 'Edit {Entity}' : 'New {Entity}' }} />
136
- <EntityPage.Content>
137
- <EntityPage.MainContent>{/* form cards — see dist/docs/EntityPage.md */}</EntityPage.MainContent>
138
- </EntityPage.Content>
139
- </EntityPage>
140
- );
141
- };
142
- ```
143
-
144
- `useEntityPage`'s own docs are explicit about both details above: `parentPath` "Must be passed if using Patterns Router" (`parentPageId` is the non-router alternative — irrelevant here, since Case B/D always uses the router), and `isNewEntity` should be "a getter when the route can change while the page stays mounted" — exactly this component's case, since a successful create typically navigates `/new` → `/:newId`.
145
-
146
- **`UseEntityPageParams` is a `Pick<>`, so a param missing from it is a compile error, not an ignored prop.** `isNewEntity` is in that list at 1.470.0 and absent before ~1.46x. If it doesn't compile, read the pick list in your installed `dist/types/hooks/useEntityPage.d.ts` and upgrade — deleting the line makes a create route behave as an edit.
147
-
148
- ## 4. Read-only detail route — Case A
149
-
150
- A display-only collection still routes its rows to a page — but not an `EntityPage`, which has no
151
- read-only mode and still has none at **1.471.0**: `fetch` and `onSave` are both required, and
152
- `actionsBarConfig` (`saveCta.text`, `cancelCta`) can retext Save but not remove it. It would ship
153
- two buttons that either do nothing or save a form nobody was offered.
154
-
155
- Use a WDS `Page` — what Cairo does itself: `example-bm`'s `DataExtensionEntityViewPage` and
156
- auto-patterns' `ViewModeEntityPage` are both plain WDS `Page`s, the latter unexported.
157
-
158
- ```tsx
159
- // {Feature}DetailPage.tsx — Case A
160
- import { useEffect, useState } from 'react';
161
- import { useParams } from 'react-router-dom';
162
- import { Card, Loader, Page, Text } from '@wix/design-system';
163
- import { usePatternsNavigate } from '@wix/patterns/router';
164
- import { fetch{Entity}, type {Entity} } from './{feature}-api';
165
-
166
- export const {Feature}DetailPage = () => {
167
- const { id } = useParams<{ id: string }>();
168
- const { navigateToCollectionPage } = usePatternsNavigate();
169
- const [entity, setEntity] = useState<{Entity} | undefined>();
170
-
171
- useEffect(() => {
172
- if (id) {
173
- fetch{Entity}(id).then(setEntity);
174
- }
175
- }, [id]);
176
-
177
- return (
178
- <Page>
179
- {/* The way back is the header's back button — a read-only page has no Cancel. */}
180
- <Page.Header
181
- title={entity?.name ?? '{Entity}'}
182
- showBackButton
183
- onBackClicked={() => navigateToCollectionPage({ path: '/' })}
184
- />
185
- <Page.Content>
186
- {entity ? (
187
- <Card>
188
- <Card.Header title="Details" />
189
- <Card.Divider />
190
- <Card.Content>
191
- <Text>{entity.name}</Text>
192
- </Card.Content>
193
- </Card>
194
- ) : (
195
- <Loader />
196
- )}
197
- </Page.Content>
198
- </Page>
199
- );
200
- };
201
- ```
202
-
203
- If the request later grows an edit form this becomes §3's `EntityPage`, route type and `onRowClick`
204
- unchanged — which is why Case A registers `editEntity`, not `other`.
@@ -1,44 +0,0 @@
1
- # Draft Template — Settings page (Cases C and D)
2
-
3
- > Pick your case in [DRAFT_TEMPLATE.md](DRAFT_TEMPLATE.md) first; this page covers Case C or D.
4
-
5
- ## The settings page
6
-
7
- No dedicated toolkit file exists for this yet (unlike Entity/Collection) — the shape, verified against `useSettingsPage.md`:
8
-
9
- ```tsx
10
- // {Feature}SettingsPage.tsx — Case C or D
11
- import { SettingsPage, useSettingsPage, useSettings } from '@wix/patterns';
12
- import { useForm } from '@wix/patterns/form';
13
- import { fetch{Feature}Settings, save{Feature}Settings, type {Feature}Settings } from './{feature}-api';
14
-
15
- // The form's shape is yours to declare, and need not match the saved settings type —
16
- // one field per control on the page.
17
- type {Feature}SettingsFormFields = { reminderHours: number; notifyStaff: boolean };
18
-
19
- export const {Feature}SettingsPage = () => {
20
- const form = useForm<{Feature}SettingsFormFields>();
21
-
22
- const state = useSettingsPage<{Feature}Settings, {Feature}SettingsFormFields>({
23
- form,
24
- fetch: () => fetch{Feature}Settings().then((settings) => ({ settings })),
25
- onSave: () => save{Feature}Settings(form.getValues()),
26
- onCancel: async () => form.reset(),
27
- saveSuccessToast: 'Settings saved',
28
- saveErrorToast: (err, { retry }) => ({ message: 'Failed to save settings', action: { text: 'Retry', onClick: retry } }),
29
- });
30
-
31
- const settings = useSettings<{Feature}Settings, {Feature}SettingsFormFields>(state);
32
-
33
- return (
34
- <SettingsPage state={state}>
35
- <SettingsPage.Header title={{ text: '{Settings Page Title}' }} />
36
- <SettingsPage.Content>
37
- <SettingsPage.MainContent>{/* form cards — same field-controller patterns as EntityPage */}</SettingsPage.MainContent>
38
- </SettingsPage.Content>
39
- </SettingsPage>
40
- );
41
- };
42
- ```
43
-
44
- `useSettingsPage`'s params are all required except `saveSuccessToast`/`saveErrorToast`: `form`, `fetch`, `onSave`, `onCancel`. There is no `parentPath`/`parentPageId` — a settings page isn't reached by drilling into a row, so it carries no back-navigation contract, and Case C needs no router at all to reach it (Section 1's entry renders it directly).