@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 +1 -1
- package/skills/wix-app/SKILL.md +12 -13
- package/skills/wix-app/references/BUSINESS_MANAGER_THEME.md +1 -2
- package/skills/wix-app/references/BUSINESS_MANAGER_TOKENS.md +1 -1
- package/skills/wix-app/references/DASHBOARD_PAGE.md +11 -11
- package/skills/wix-app/references/WIX_PATTERNS_DOCS.md +3 -2
- package/skills/wix-app/references/dashboard-page/DRAFT_TEMPLATE.md +65 -68
- package/skills/wix-app/references/data-collection/LIFECYCLE.md +6 -4
- package/skills/wix-app/references/dashboard-page/DRAFT_TEMPLATE_COLLECTION.md +0 -184
- package/skills/wix-app/references/dashboard-page/DRAFT_TEMPLATE_ROUTER.md +0 -204
- package/skills/wix-app/references/dashboard-page/DRAFT_TEMPLATE_SETTINGS.md +0 -44
package/package.json
CHANGED
package/skills/wix-app/SKILL.md
CHANGED
|
@@ -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 ([
|
|
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):**
|
|
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)
|
|
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
|
|
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
|
|
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
|
-
|
|
110
|
-
|
|
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
|
|
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
|
-
- [ ] **
|
|
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,
|
|
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 — [
|
|
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,
|
|
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
|
-
**
|
|
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:**
|
|
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
|
|
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
|
|
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 |
|
|
95
|
+
| Request | Template | Adapt |
|
|
96
96
|
| --- | --- | --- |
|
|
97
|
-
| "Dashboard page to manage blog posts" |
|
|
98
|
-
| "Settings page for notification preferences" |
|
|
99
|
-
| "Admin panel for customer orders" |
|
|
100
|
-
| "Settings page for the coupon popup embedded script" |
|
|
101
|
-
| "Admin page to manage fees, with an app settings section" |
|
|
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
|
|
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.**
|
|
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
|
-
|
|
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
|
-
##
|
|
7
|
+
## 1. Find the templates
|
|
8
8
|
|
|
9
|
-
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
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
|
-
|
|
29
|
-
`fetchData`, the filters and the columns for both.
|
|
22
|
+
## 2. Choose, then read the chosen template's page
|
|
30
23
|
|
|
31
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
-
|
|
66
|
-
<BusinessManagerTheme>
|
|
67
|
-
<WixPatternsProvider>
|
|
68
|
-
<{Feature}SettingsPage />
|
|
69
|
-
</WixPatternsProvider>
|
|
70
|
-
</BusinessManagerTheme>
|
|
71
|
-
);
|
|
55
|
+
### Installing the CMS pair
|
|
72
56
|
|
|
73
|
-
|
|
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
|
-
|
|
66
|
+
**Either way, confirm a single copy:**
|
|
77
67
|
|
|
78
|
-
|
|
68
|
+
```bash
|
|
69
|
+
npm dedupe
|
|
70
|
+
find node_modules -path '*@wix/patterns/package.json' -not -path '*/dist/*'
|
|
71
|
+
```
|
|
79
72
|
|
|
80
|
-
|
|
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
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
91
|
+
## 5. What this skill adds to every template
|
|
93
92
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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).
|