@spree/docs 0.1.281 → 0.1.283

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.
@@ -151,7 +151,8 @@ Seven checks run on every cart read:
151
151
  > **WARNING:** **An empty `requirements` array does not guarantee completion will succeed.** What a
152
152
  > cart reports is the *advisory* set. Completing one additionally checks stock
153
153
  > (`out_of_stock`, `discontinued`), negotiated quantity rules
154
- > (`quantity_rule_violated`) and the guest-checkout policy
154
+ > (`quantity_rule_violated`), whether each product is still in the buyer's
155
+ > [catalogs](catalogs.md) (`not_orderable`) and the guest-checkout policy
155
156
  > (`guest_checkout_not_allowed`) — each of which has to load line items, so they are
156
157
  > held back to keep reading a cart cheap. Treat a failed completion as a normal path,
157
158
  > not an exception.
@@ -169,6 +169,8 @@ If one applicable catalog is a pricing overlay, the restriction is off and the s
169
169
 
170
170
  Gated storefront access is checked before any of this — a shopper who has to sign in never reaches catalog resolution.
171
171
 
172
+ What a shopper can see is also what they can buy. Adding a product outside their catalogs, or one not published on the cart's channel, answers `404` — the same as reading that product. A cart can still come to hold one without an add: a guest cart claimed after signing in, a cart switched to another company, or a catalog edited while the cart was open. Completing checkout checks every line again and refuses such a cart with a `not_orderable` [requirement](carts.md) naming the product, until the shopper removes it. Free items a promotion adds are no exception, so keep gift products inside the catalogs of the buyers the promotion targets. Staff keying in a draft order are not restricted.
173
+
172
174
  > **WARNING:** **A company buyer never picks up their customer group's catalogs.** As soon as any catalog is assigned to their company or one above it, that is their agreement, and group assignments are not consulted for them.
173
175
  >
174
176
  > So don't express trade tiers as customer groups over a company tree — the tier catalogs would be unreachable for exactly the buyers they were meant for. Model tiers as company assignments: the group-wide range on the root, and each tier's catalog on the member companies or divisions in that tier. One tier catalog can carry as many company assignments as the tier has members, and nearest-first pricing means a buyer's own node beats anything inherited.
@@ -209,6 +209,19 @@ already permits replaces its filter rather than widening it.
209
209
  > which need no code and are filterable and sortable. This is for extensions
210
210
  > that add real database columns.
211
211
 
212
+ ### Writable by sellers
213
+
214
+ The Seller API accepts a narrower set of attributes than the Admin API, so the
215
+ list above does not apply to it — an attribute you open to operators stays
216
+ operator-only. To let sellers write it too, for example from a field on the
217
+ seller panel's product page, declare it again on the seller list:
218
+
219
+ ```ruby config/initializers/spree.rb
220
+ Spree::Product.additional_seller_permitted_attributes += [:brand_id]
221
+ ```
222
+
223
+ Only declare attributes a seller may safely set on their own records.
224
+
212
225
  Two endpoints deliberately ignore this hook because their parameters are
213
226
  authorization data rather than resource data: API keys and invitations.
214
227
 
@@ -77,7 +77,7 @@ The entry-level `if` predicate also exists, but it only receives the slot's own
77
77
 
78
78
  ## Binding to the host form
79
79
 
80
- Slots on form pages (product, category, store settings — see the [slots catalog](../slots-catalog.md#detail-page-form-slots) for which) render **inside the page's `<form>`** and expose its react-hook-form instance. A widget can register inputs against it with `useHostForm()` — they hydrate from the API, flip the Save button on change, and persist in the page's own save, with no save logic in the widget:
80
+ Slots on form pages (product, category, collection, catalog, price list, promotion, store settings — see the [slots catalog](../slots-catalog.md#detail-page-slots) for which) render **inside the page's `<form>`** and expose its react-hook-form instance. A widget can register inputs against it with `useHostForm()` — they hydrate from the API, flip the Save button on change, and persist in the page's own save, with no save logic in the widget:
81
81
 
82
82
  ```tsx
83
83
  import { useHostForm } from '@spree/dashboard'
@@ -157,7 +157,7 @@ Extension fields are validated by the **server** — a Rails validation failing
157
157
 
158
158
  ### Which forms support this
159
159
 
160
- `useHostForm()` works wherever a built-in form exposes its form context — currently the **product** (edit + new), **category** (edit + new), and **store settings** forms, with matching slots (`product.form_sidebar`, `category.form_sidebar`, `store.form_main`) and form keys (`product`, `category`, `store`). Orders and customers have no page-wide form — their sheets own their edits — so slot widgets there manage their own persistence; use `useOptionalHostForm()` to write a widget that adapts to both contexts.
160
+ `useHostForm()` works wherever a built-in form exposes its form context — currently the **product** (edit + new), **category** (edit + new), **collection**, **catalog**, **price list** (edit + new), **promotion** (edit + new), and **store settings** forms, with matching slots (`product.form_sidebar`, `category.form_sidebar`, `collection.form_sidebar`, `catalog.form_sidebar`, `price_list.form_sidebar`, `promotion.form_sidebar`, `store.form_main`) and form keys (`product`, `category`, `collection`, `catalog`, `price_list`, `promotion`, `store`). The [slots catalog](../slots-catalog.md#detail-page-slots) marks which pages have a host form. Orders and customers have no page-wide form — their sheets own their edits — so slot widgets there manage their own persistence; use `useOptionalHostForm()` to write a widget that adapts to both contexts.
161
161
 
162
162
  ## Which path should I pick?
163
163
 
@@ -4,10 +4,36 @@ sidebarTitle: Slots catalog
4
4
  description: Every named slot the dashboard renders today. Each entry lists the host page, the slot name, and the context the slot's components receive.
5
5
  ---
6
6
 
7
- This is the canonical list of slots the dashboard currently exposes. The source of truth is the `<Slot name="…">` call sites in the dashboard source (linked under Reference below); each entry here records the host page, the slot name, and the context your component receives.
7
+ This is the canonical list of slots the admin dashboard and the seller panel currently expose. The source of truth is the `<Slot name="…">` call sites in the dashboard source (linked under Reference below); each entry here records the host page, the slot name, and the context your component receives.
8
8
 
9
9
  If you need a new injection point in a built-in page, open a PR adding `<Slot name="..." context={...} />` and a documentation entry here — that's the contract for new slots.
10
10
 
11
+ ## At a glance
12
+
13
+ | Area | Page | Slots |
14
+ |---|---|---|
15
+ | Every page | Page header | `page.actions`, `page.actions_dropdown` |
16
+ | Every page | Tabbed sub-navigation | `page.tabs` |
17
+ | Products | Product | `product.form_sidebar` |
18
+ | Products | Category | `category.form_sidebar` |
19
+ | Products | Collection | `collection.form_sidebar` |
20
+ | Products | Catalog | `catalog.form_sidebar` |
21
+ | Products | Price list | `price_list.form_sidebar`, `price_list.rule_form.<type>` |
22
+ | Orders | Order | `order.form_sidebar` |
23
+ | Customers | Customer | `customer.form_sidebar` |
24
+ | Customers | Company | `company.form_main`, `company.form_sidebar`, `company_membership.*` |
25
+ | Promotions | Promotion | `promotion.form_sidebar`, `promotion.rule_form.<type>`, `promotion.action_form.<type>` |
26
+ | Inventory | Purchase order | `purchase_order.form_sidebar` |
27
+ | Inventory | Stock transfer | `stock_transfer.form_sidebar` |
28
+ | Sellers | Seller | `seller.form_main`, `seller.form_sidebar` |
29
+ | Sellers | Seller payout | `seller_payout.form_main`, `seller_payout.form_sidebar` |
30
+ | Settings | Store | `store.form_main` |
31
+ | Settings | Payment method | `payment_method.*.<provider>` |
32
+ | Settings | Webhook endpoint | `webhook_endpoint.form_sidebar` |
33
+ | Onboarding | Getting started | `getting-started.task.<name>` |
34
+ | Sign-in | No store access | `no_store_access` |
35
+ | Seller panel | Product, order, payout, profile, team | `seller.product.*`, `seller.order.*`, `seller.payout.*`, `seller.profile.*`, `seller.team.*` |
36
+
11
37
  ## Ambient context
12
38
 
13
39
  Ambient context (`permissions`, `store`, `user` merged into every slot's props) is planned but **not wired up yet** — today slot components receive only the slot-specific context listed below. Until it lands, read those values with the hooks instead:
@@ -43,13 +69,30 @@ Rendered by `<PageHeader>` (`@spree/dashboard-core/components/page-header.tsx`)
43
69
  | Use | Add menu items (`<DropdownMenuItem>`) for secondary actions |
44
70
  | Context | `{ resource, ...slotContext }` — same as `page.actions` |
45
71
 
46
- ## Detail-page form slots
72
+ ## Page tabs slot
73
+
74
+ ### `page.tabs` (default)
75
+
76
+ Rendered by `<PageTabs>` at the right edge of any tabbed sub-nav.
77
+
78
+ | Where | After the built-in tab strip |
79
+ |---|---|
80
+ | Use | Append your own tab(s) to a sub-route — typically for plugin-owned views inside an existing resource (e.g., "Returns" tab on the order detail) |
81
+ | Context | `{ tabs, ...slotContext }` — `tabs` is the array of built-in tabs (so you can inspect or filter by them) |
82
+
83
+ Some pages override `slotName` to scope tabs to a single resource (e.g., `slotName="order.tabs"`). The catalog will grow these as we wire them up; today only the default `page.tabs` name is in production use.
84
+
85
+ ## Detail-page slots
86
+
87
+ Every resource detail page renders a slot at the end of its sidebar column, and some also at the end of the main column. The slot name starts with the resource name, and the context key matches it.
47
88
 
48
- The resource detail pages each render a slot below their built-in cards. The context key matches the resource name.
89
+ **Host form.** On pages marked *host form: yes*, the slot renders inside the page's own `<form>` and exposes it — widgets can bind inputs via [`useHostForm()`](recipes/custom-form-field.md) that hydrate, dirty-track, and save with the page's Save button. The form key is what you register extension fields under in `formFields`. On every other page, widgets own their persistence; use `useOptionalHostForm()` to write a widget that adapts to both.
49
90
 
50
- **Host form:** on pages marked *host form: yes*, the slot renders inside the page's own `<form>` and the form context is exposed — widgets can bind inputs via [`useHostForm()`](recipes/custom-form-field.md) that hydrate, dirty-track, and save with the page's Save button. On pages without one, widgets own their persistence (use `useOptionalHostForm()` to adapt).
91
+ > **NOTE:** On host-form pages your widget renders inside the page's `<form>` element, so a `<Button>` in it submits the page unless you give it `type="button"`.
51
92
 
52
- ### `product.form_sidebar`
93
+ ### Products
94
+
95
+ #### `product.form_sidebar`
53
96
 
54
97
  | Where | Product detail page (`products/$productId`), end of the sidebar column |
55
98
  |---|---|
@@ -57,7 +100,7 @@ The resource detail pages each render a slot below their built-in cards. The con
57
100
  | Context | `{ product }` — the full `Product` record from the Admin API |
58
101
  | Host form | **Yes** — form key `product` |
59
102
 
60
- ### `category.form_sidebar`
103
+ #### `category.form_sidebar`
61
104
 
62
105
  | Where | Category detail page (`products/categories/$categoryId`), end of the sidebar column |
63
106
  |---|---|
@@ -65,15 +108,33 @@ The resource detail pages each render a slot below their built-in cards. The con
65
108
  | Context | `{ category }` — the `Category` record (may be briefly `undefined` while refetching) |
66
109
  | Host form | **Yes** — form key `category` |
67
110
 
68
- ### `store.form_main`
111
+ #### `collection.form_sidebar`
69
112
 
70
- | Where | Store settings page (`settings/store`), end of the main column |
113
+ | Where | Collection detail page (`products/collections/$collectionId`), end of the sidebar column |
71
114
  |---|---|
72
- | Use | Store-level settings a plugin owns — integration toggles, account linking |
73
- | Context | `{ store }` — the full `Store` record |
74
- | Host form | **Yes** — form key `store` |
115
+ | Use | Collection-scoped cards — merchandising rules, feed settings, external sync state |
116
+ | Context | `{ collection }` — the `Collection` record |
117
+ | Host form | **Yes** — form key `collection` |
75
118
 
76
- ### `order.form_sidebar`
119
+ #### `catalog.form_sidebar`
120
+
121
+ | Where | Catalog detail page (`products/catalogs/$catalogId`), end of the sidebar column |
122
+ |---|---|
123
+ | Use | B2B catalog cards — ERP contract references, approval state, export settings |
124
+ | Context | `{ catalog, canEdit }` — the `Catalog` record; `canEdit` is whether the viewer may update it |
125
+ | Host form | **Yes** — form key `catalog` |
126
+
127
+ #### `price_list.form_sidebar`
128
+
129
+ | Where | Price list create and detail pages (`products/price-lists/new`, `products/price-lists/$priceListId`), end of the sidebar column |
130
+ |---|---|
131
+ | Use | Price-list cards — sync state with an external pricing engine, audit notes |
132
+ | Context | `{ priceList, mode }` — `mode` is `'create'` or `'edit'`; `priceList` is `undefined` in create mode |
133
+ | Host form | **Yes** — form key `price_list` |
134
+
135
+ ### Orders and customers
136
+
137
+ #### `order.form_sidebar`
77
138
 
78
139
  | Where | Order detail page (`orders/$orderId`), end of the sidebar column |
79
140
  |---|---|
@@ -81,7 +142,7 @@ The resource detail pages each render a slot below their built-in cards. The con
81
142
  | Context | `{ order }` — the full `Order` record |
82
143
  | Host form | No — widgets save via their own API calls |
83
144
 
84
- ### `customer.form_sidebar`
145
+ #### `customer.form_sidebar`
85
146
 
86
147
  | Where | Customer detail page (`customers/$customerId`), end of the sidebar column |
87
148
  |---|---|
@@ -89,23 +150,15 @@ The resource detail pages each render a slot below their built-in cards. The con
89
150
  | Context | `{ customer }` — the full `Customer` record |
90
151
  | Host form | No — the page edits through sheets; widgets save via their own API calls |
91
152
 
92
- ### `collection.form_sidebar`
93
-
94
- | Where | Collection detail page (`products/collections/$collectionId`), end of the sidebar column |
95
- |---|---|
96
- | Use | Collection-scoped cards — merchandising rules, feed settings, external sync state |
97
- | Context | `{ collection }` — the `Collection` record |
98
- | Host form | **Yes** — form key `collection` |
99
-
100
- ### `company.form_main`
153
+ #### `company.form_main`
101
154
 
102
- | Where | Company detail page, end of the main column |
155
+ | Where | Company detail page (`companies/$companyId`), end of the main column |
103
156
  |---|---|
104
157
  | Use | B2B account cards — credit terms, approval settings, external account references |
105
158
  | Context | `{ company, kind, canEdit }` — `kind` is the company's own `kind`, `canEdit` whether the viewer may write |
106
159
  | Host form | No — the widget owns its persistence |
107
160
 
108
- ### `company.form_sidebar`
161
+ #### `company.form_sidebar`
109
162
 
110
163
  | Where | Company detail page, end of the sidebar column |
111
164
  |---|---|
@@ -113,81 +166,187 @@ The resource detail pages each render a slot below their built-in cards. The con
113
166
  | Context | `{ company, kind, canEdit }` |
114
167
  | Host form | No |
115
168
 
116
- ## Seller panel slots
169
+ #### Company membership slots
170
+
171
+ The company's member list and its "Add member" sheet carry three smaller slots, built for an extension that gives members roles.
172
+
173
+ | Slot | Where | Context |
174
+ |---|---|---|
175
+ | `company_membership.row_meta` | Under each member's email in the member list | `{ membership, companyId, canEdit }` |
176
+ | `company_membership.row_actions` | Each member's actions menu, above "Remove member" — render `<DropdownMenuItem>`s | `{ membership, companyId }` |
177
+ | `company_membership.form_fields` | The "Add member" sheet, below the email field. **Replaces** a built-in note about roles | `{ companyId, onChange }` — call `onChange(params)` and those params are merged into the request that adds the member |
178
+
179
+ ### Promotions
117
180
 
118
- Rendered by the marketplace seller panel (`@spree/seller-dashboard`) rather than the admin dashboard, and registered the same way.
181
+ #### `promotion.form_sidebar`
119
182
 
120
- ### `seller.form_main`
183
+ | Where | Promotion create and detail pages (`promotions/new`, `promotions/$promotionId`), end of the sidebar column |
184
+ |---|---|
185
+ | Use | Campaign attribution, budget tracking, external coupon-system sync |
186
+ | Context | `{ promotion, mode }` — `mode` is `'create'` or `'edit'`; `promotion` is `undefined` in create mode |
187
+ | Host form | **Yes** — form key `promotion` |
188
+
189
+ ### Inventory
190
+
191
+ #### `purchase_order.form_sidebar`
121
192
 
122
- | Where | Seller detail page, end of the main column |
193
+ | Where | Purchase order detail page (`purchase-orders/$purchaseOrderId`), end of the sidebar column |
194
+ |---|---|
195
+ | Use | Supplier portal links, ERP sync status, landed-cost notes |
196
+ | Context | `{ purchaseOrder }` — the full `PurchaseOrder` record |
197
+ | Host form | No |
198
+
199
+ #### `stock_transfer.form_sidebar`
200
+
201
+ | Where | Stock transfer detail page (`transfers/$transferId`), end of the sidebar column |
202
+ |---|---|
203
+ | Use | Carrier tracking, warehouse-system sync status |
204
+ | Context | `{ transfer }` — the full `StockTransfer` record |
205
+ | Host form | No |
206
+
207
+ ### Sellers (marketplace)
208
+
209
+ These slots are on the store owner's seller management pages in the admin dashboard. For the seller's own view, see the seller panel slots below.
210
+
211
+ #### `seller.form_main`
212
+
213
+ | Where | Seller detail page (`sellers/$sellerId`), end of the main column |
123
214
  |---|---|
124
215
  | Use | Seller-scoped cards — payout configuration, onboarding state, compliance records |
125
- | Context | `{ seller, canEdit }` |
216
+ | Context | `{ seller, canEdit }` — the full `Seller` record; `canEdit` is whether the viewer may update it |
126
217
  | Host form | No |
127
218
 
128
- ### `seller.form_sidebar`
219
+ #### `seller.form_sidebar`
129
220
 
130
221
  | Where | Seller detail page, end of the sidebar column |
131
222
  |---|---|
132
- | Use | Narrow seller-scoped cards |
223
+ | Use | Narrow seller-scoped cards — risk score, verification status, external account links |
133
224
  | Context | `{ seller, canEdit }` |
134
225
  | Host form | No |
135
226
 
136
- ### `seller.team.actions`
227
+ #### `seller_payout.form_main`
137
228
 
138
- | Where | Seller team screen, in the header's action area |
229
+ | Where | Seller payout detail page (`sellers/payouts/$payoutId`), end of the main column |
139
230
  |---|---|
140
- | Use | Team-level actions — bulk invite, export, an external directory sync |
141
- | Context | `{ sellerId }` |
231
+ | Use | Payout-scoped cards — remittance advice, the transfer as seen by the payment provider |
232
+ | Context | `{ payout }` — the full `SellerPayout` record |
142
233
  | Host form | No |
143
234
 
144
- ### `seller.team.after`
235
+ #### `seller_payout.form_sidebar`
145
236
 
146
- | Where | Seller team screen, below the member list |
237
+ | Where | Seller payout detail page, end of the sidebar column |
147
238
  |---|---|
148
- | Use | Anything that belongs under the team — audit log, role explainer, seat usage |
149
- | Context | `{ sellerId }` |
239
+ | Use | Accounting-system sync status, links to the provider's dashboard |
240
+ | Context | `{ payout }` |
150
241
  | Host form | No |
151
242
 
152
- ## Page tabs slot
243
+ ### Settings
153
244
 
154
- ### `page.tabs` (default)
245
+ #### `store.form_main`
155
246
 
156
- Rendered by `<PageTabs>` at the right edge of any tabbed sub-nav.
247
+ | Where | Store settings page (`settings/store`), end of the main column |
248
+ |---|---|
249
+ | Use | Store-level settings a plugin owns — integration toggles, account linking |
250
+ | Context | `{ store }` — the full `Store` record |
251
+ | Host form | **Yes** — form key `store` |
157
252
 
158
- | Where | After the built-in tab strip |
253
+ #### `webhook_endpoint.form_sidebar`
254
+
255
+ | Where | Webhook endpoint detail page (`settings/webhooks/$webhookEndpointId`), end of the sidebar column |
159
256
  |---|---|
160
- | Use | Append your own tab(s) to a sub-route — typically for plugin-owned views inside an existing resource (e.g., "Returns" tab on the order detail) |
161
- | Context | `{ tabs, ...slotContext }` — `tabs` is the array of built-in tabs (so you can inspect or filter by them) |
257
+ | Use | Notes on the receiving system, links to its logs, a "send test event" action |
258
+ | Context | `{ endpoint }` — the full `WebhookEndpoint` record |
259
+ | Host form | No |
162
260
 
163
- Some pages override `slotName` to scope tabs to a single resource (e.g., `slotName="order.tabs"`). The catalog will grow these as we wire them up; today only the default `page.tabs` name is in production use.
261
+ ## Seller panel slots
262
+
263
+ Rendered by the marketplace seller panel (`@spree/seller-dashboard`) rather than the admin dashboard, and registered the same way, in the seller panel's own plugins. Their names start with `seller.` followed by the page — `seller.form_main` and `seller.form_sidebar` above belong to the admin dashboard instead.
264
+
265
+ The product page is a host form with the form key `seller.product`: fields a widget binds with `useHostForm()` save with the product. The Seller API accepts only the attributes your extension declares in `additional_seller_permitted_attributes` — see [Writable by sellers](../customization/api.md#writable-by-sellers). The other seller panel pages have no page-wide form, so widgets there save via their own Seller API calls.
266
+
267
+ | Slot | Where | Context |
268
+ |---|---|---|
269
+ | `seller.product.form_sidebar` | Product create and edit pages, end of the sidebar column | `{ product, mode }` — `mode` is `'new'` or `'edit'`; `product` is `undefined` while creating |
270
+ | `seller.order.form_sidebar` | Order detail page, end of the sidebar column | `{ order }` |
271
+ | `seller.payout.form_sidebar` | Payout detail page, end of the sidebar column | `{ payout }` |
272
+ | `seller.profile.form_main` | Profile page, end of the main column | `{ profile }` |
273
+ | `seller.profile.form_sidebar` | Profile page, end of the sidebar column | `{ profile }` |
274
+ | `seller.team.actions` | Team page, in the header's action area — bulk invite, export, an external directory sync | `{ sellerId }` |
275
+ | `seller.team.after` | Team page, below the member list — audit log, role explainer, seat usage | `{ sellerId }` |
276
+
277
+ ## Replaceable slots
278
+
279
+ These slots render built-in content when nothing is registered. Registering a component **replaces** that content rather than adding to it.
280
+
281
+ ### `no_store_access`
282
+
283
+ | Where | The screen shown to a signed-in admin who has a role on no store |
284
+ |---|---|
285
+ | Use | Replace the built-in "no access" message — for example, with a request-access form or a link to your own onboarding |
286
+ | Context | `{ user, signOut }` — `user` is the signed-in admin; `signOut()` ends the session |
287
+
288
+ Import the name as `NO_STORE_ACCESS_SLOT` from `@spree/dashboard-core` rather than typing it.
289
+
290
+ ### `getting-started.task.<name>`
291
+
292
+ | Where | The body of one step on the Getting Started checklist (`getting-started`) |
293
+ |---|---|
294
+ | Use | Replace a step's description and link with your own flow — or give a setup task your extension added on the backend its own UI |
295
+ | Context | `{ task, store, storeId }` — `task` is the `SetupTask` record (`name`, `done`, …) |
296
+
297
+ `<name>` is the task's `name` as the API returns it.
298
+
299
+ ## Editor slots (dynamic)
300
+
301
+ Promotions, price lists, and payment methods each pick their editor by type. The slot names are computed from the type's `api_type` shorthand, so registering against the right name is what hooks your editor in. The promotion and price list editor slots **replace** the built-in editor, which is generated from the type's preference schema.
302
+
303
+ ### `promotion.rule_form.<type>`
304
+
305
+ | Where | The sheet that edits one promotion rule (e.g. `promotion.rule_form.product`) |
306
+ |---|---|
307
+ | Use | A custom editor for a rule whose settings don't fit the generated form |
308
+ | Context | `{ draft, onSave, onClose }` — edit `draft` locally and call `onSave(next)`; the promotion saves everything when the merchant clicks Save |
309
+
310
+ ### `promotion.action_form.<type>`
311
+
312
+ | Where | The sheet that edits one promotion action (e.g. `promotion.action_form.free_shipping`) |
313
+ |---|---|
314
+ | Use | A custom editor for an action — calculators, line-item pickers |
315
+ | Context | `{ draft, onSave, onClose }` — same as rules |
316
+
317
+ ### `price_list.rule_form.<type>`
318
+
319
+ | Where | The sheet that edits one price list rule (e.g. `price_list.rule_form.volume_rule`) |
320
+ |---|---|
321
+ | Use | A custom editor for a price rule |
322
+ | Context | `{ draft, onSave, onClose }` — same as promotion rules |
164
323
 
165
- ## Payment method editor slots (dynamic)
324
+ ### Payment method editor slots
166
325
 
167
326
  Used by `<PaymentMethodForm>` to let payment-provider plugins replace pieces of the editor sheet. The slot names are computed per provider type (`stripe`, `bogus`, …), so registering against the right name is what hooks your editor in.
168
327
 
169
- ### `payment_method.guide.<providerType>`
328
+ #### `payment_method.guide.<providerType>`
170
329
 
171
330
  | Where | Above the preferences form |
172
331
  |---|---|
173
332
  | Use | Banner explaining what the integration does, what credentials to use, links to provider docs |
174
333
  | Context | `PaymentMethodEditorContext` (see below) |
175
334
 
176
- ### `payment_method.form.<providerType>`
335
+ #### `payment_method.form.<providerType>`
177
336
 
178
337
  | Where | In place of the auto-generated preferences form |
179
338
  |---|---|
180
339
  | Use | Render a custom React form when the provider's preferences are too complex for the generated UI (multi-step OAuth, environment switchers, …) |
181
340
  | Context | `PaymentMethodEditorContext` |
182
341
 
183
- ### `payment_method.actions.<providerType>`
342
+ #### `payment_method.actions.<providerType>`
184
343
 
185
344
  | Where | Sheet footer, before Save/Cancel |
186
345
  |---|---|
187
346
  | Use | Provider-specific action buttons — "Test connection", "Rotate keys", "Open dashboard in provider" |
188
347
  | Context | `PaymentMethodEditorContext` |
189
348
 
190
- ### `PaymentMethodEditorContext`
349
+ #### `PaymentMethodEditorContext`
191
350
 
192
351
  ```ts
193
352
  interface PaymentMethodEditorContext {
@@ -219,10 +378,11 @@ Use these instead of constructing the string yourself, so a rename in one place
219
378
 
220
379
  If a built-in page should expose a new injection point:
221
380
 
222
- 1. Pick a name (`<resource>.<area>`, e.g., `order.timeline`)
381
+ 1. Pick a name (`<resource>.<area>`, e.g., `order.timeline`; `seller.<page>.<area>` in the seller panel)
223
382
  2. Add `<Slot name="..." context={{ resource: order, /* ... */ }} />` at the call site
224
- 3. Document the slot here — host, intent, context shape
225
- 4. Open the PR
383
+ 3. If the slot sits inside the page's form, make it a host form: wrap the form in `<FormProvider>`, hydrate with `extensionFormValues('<resource>', record)`, and merge `extensionSubmitValues('<resource>', form)` into the save payload — the collection page is the reference
384
+ 4. Document the slot here — host, intent, context shape, and form key
385
+ 5. Open the PR
226
386
 
227
387
  The slot is not "live" until the docs land — without an entry here, no plugin author knows it exists.
228
388
 
@@ -684,7 +684,7 @@ Import everything from `@spree/seller-dashboard` — it re-exports both the fram
684
684
  | Change wording | [Translations](../dashboard/customization/translations.md) merged with `i18n.addResourceBundle` |
685
685
  | Install a third-party plugin | `npm add <plugin>` and restart — the Vite plugin picks it up from your dependencies |
686
686
 
687
- The seller panel currently exposes just two slots, both on its team page: `seller.team.actions` and `seller.team.after`. (`seller.form_main` and `seller.form_sidebar` sound related but belong to the *admin* dashboard's seller detail page — they are for the operator's view of a seller, not the seller's own.) See the [slots catalog](../dashboard/slots-catalog.md) for their contexts. To put a widget anywhere else in the panel, add a route rather than looking for a slot that is not there yet.
687
+ The seller panel exposes slots on its product, order, payout, profile, and team pages — `seller.product.form_sidebar`, `seller.order.form_sidebar`, `seller.payout.form_sidebar`, `seller.profile.form_main` and `seller.profile.form_sidebar`, plus `seller.team.actions` and `seller.team.after`. The operator's side has its own: `seller.form_main` and `seller.form_sidebar` on the admin dashboard's seller page, and `seller_payout.form_main` and `seller_payout.form_sidebar` on its payout page. See the [slots catalog](../dashboard/slots-catalog.md#seller-panel-slots) for each one's context. To put a widget anywhere else in the panel, add a route.
688
688
 
689
689
  > **NOTE:** **White-labelling is theming and copy, not a branding config object.** There is no logo or brand-name setting to fill in. You restyle by owning the Tailwind layer in your host app, and you re-word by overriding translation keys. Both are real customization paths — there is simply no shortcut that skips them.
690
690
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spree/docs",
3
- "version": "0.1.281",
3
+ "version": "0.1.283",
4
4
  "description": "Spree Commerce developer documentation for AI agents and local reference",
5
5
  "type": "module",
6
6
  "license": "CC-BY-4.0",