@souldi/try-on 0.5.0 → 0.5.2

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/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # Changelog
2
2
 
3
+ ## Unreleased
4
+
5
+ **Breaking.** Refine and the size screen leave the widget (ADR-0095).
6
+
7
+ - **One screen.** The result dialog is one **Try-on** screen. Before a try-on it shows the garment, the customer's size, the cart button and **Try on**; while the try-on is made the garment fades under a progress line, "Creating your try-on…", and the dialog stays open until the result lands. The size on the button opens it, before and after a try-on.
8
+ - **Removed: the size screen.** "How size X sits on you" and its 3D mannequin are gone from the dialog, and the size in the dialog is no longer a button. The mannequin's standalone build is still published, for older bundles that load it.
9
+ - **Removed: Refine.** The dialog has no Refine control, and the button handle's `refine()` is gone. `onError` no longer reports `operation: 'refine'`, and `onGenerationSuccess` / `onResultShown` no longer carry `refinable`. Refine stays in the Souldi API; the widget never sends a `refinement_prompt`.
10
+ - `openSize()` on the button handle still opens the dialog on the customer's size. Before a try-on it now shows the garment and **Try on** there.
11
+ - **New: between two sizes, the customer picks.** When the customer is between two neighbouring sizes, the dialog shows "Your size: between M and L" with two options, M for a closer fit and L for a looser one, the recommended one preselected, and "Both will fit you." under them. **Add size X to cart** follows the choice; a new recommendation (edited measurements, another colour) resets it. Shown only where there is a cart button; otherwise the dialog names one size and gives the same advice in words. A customer whose second size is not a neighbour still sees one size, now captioned "This one is harder to call." alone (it used to follow "From your measurements.").
12
+ - **`onAddToCart` gains `recommendedSize` and `recommendationId`** beside `size`, which is the size the customer chose. Additive: a handler that reads `size` alone keeps working. See [`onAddToCart`](README.md#onaddtocart).
13
+
3
14
  ## 0.5.0
4
15
 
5
16
  **Breaking.** `init({ appearance })` replaces the `theme` option.
package/README.md CHANGED
@@ -61,19 +61,20 @@ The result surface is the widget's and only the widget's: an accessible
61
61
  `<dialog>` inside an isolated Shadow DOM, appended to `<body>`. It reads none of
62
62
  your CSS selectors and never touches your product images, so a theme change
63
63
  cannot break it — and there is nothing to point it at. It is also where the
64
- customer decides. It has two screens:
65
-
66
- - **Try-on** — the render, beside the customer's size (when the garment has a
67
- size chart), an **Add size X to cart** button (when you supply
68
- [`onAddToCart`](#onaddtocart)), Refine, and the garment's back view when
69
- there is one.
70
- - **Size** — "How size X sits on you": the same size and cart button beside a
71
- mannequin showing how that size fits the customer's body, and a **Try on**
72
- button (or **View try-on**, once a result exists).
73
-
74
- The dialog can open on **Size** before any try-on has been generated — the
75
- size comes from the customer's measurements, not from a render — so the size
76
- shown on the try-on button opens it, before and after a try-on. The ruler
64
+ customer decides. It is one screen, **Try-on**, in three states:
65
+
66
+ - **Before a try-on** — the garment (the button's `garmentUrl`) beside the
67
+ customer's size (when the garment has a size chart), an **Add size X to
68
+ cart** button (when you supply [`onAddToCart`](#onaddtocart)) and **Try on**.
69
+ - **While it is being made** — the garment fades under a progress line,
70
+ "Creating your try-on…". The dialog stays open and shows the result when it
71
+ lands; closed meanwhile, it reopens with the result.
72
+ - **With a result** — the render, the same size and cart button, and the
73
+ garment's back view when there is one.
74
+
75
+ The dialog can open before any try-on has been generated — the size comes
76
+ from the customer's measurements, not from a render — so the size shown on
77
+ the try-on button opens it, before and after a try-on. The ruler
77
78
  shown on the button before a size exists is a way into sizing: it signs the
78
79
  customer in and takes them to their size, or to their profile when no size is
79
80
  ready yet. Neither ever starts a try-on. **Your profile** (retake the photo,
@@ -117,7 +118,7 @@ Callbacks about a specific try-on receive a trailing `info` object:
117
118
  | Field | Type | Present on | Description |
118
119
  |-------|------|-----------|-------------|
119
120
  | `buttonId` | `string` | most | The `containerId` of the button this callback is about |
120
- | `operation` | `'generate' \| 'refine' \| 'view'` | `onError`, `onResultShown` | Which operation this is about |
121
+ | `operation` | `'generate' \| 'view'` | `onError`, `onResultShown` | Which operation this is about |
121
122
  | `shownInResult` | `boolean` | `onError` | `true` when the widget has already said this inside its open dialog, under the result it failed to change — so you need not say it again |
122
123
  | `view` | `string \| null` | `onGenerationSuccess`, `onResultShown` | The Render View this result is (e.g. `'front'`), when the garment offers more than one |
123
124
  | `how` | `'verified' \| 'shared' \| 'restored'` | `onSignedIn` | How this tab's session began. Only `'verified'` is a *new* sign-in — `'shared'` and `'restored'` are this tab catching up with a session that began elsewhere |
@@ -131,12 +132,9 @@ placed by guessing. Read it whenever you render results yourself.
131
132
  `info` is passed after the existing arguments, so `onGenerationSuccess(url)`
132
133
  and `onError(message)` keep working unchanged.
133
134
 
134
- Note that `onError` with `operation: 'refine'` also covers a refine the widget
135
- *declined* — only the most recently generated result can be refined, so a
136
- request against an older one is reported here rather than silently dropped.
137
- The previous result is untouched either way. A failed `refine` or `view` with
138
- the dialog open is already shown in it (`shownInResult: true`); with the dialog
139
- closed, it is yours to show if you want the customer to see it.
135
+ A failed `view` leaves the previous result untouched. With the dialog open it
136
+ is already shown in it (`shownInResult: true`); with the dialog closed, it is
137
+ yours to show if you want the customer to see it.
140
138
 
141
139
  `onResultShown` receives the same `info` `onGenerationSuccess` did for that
142
140
  result, minus the image URL — it is about the same render, just reported once
@@ -167,17 +165,29 @@ address, or body measurements.
167
165
  #### `onAddToCart`
168
166
 
169
167
  The widget knows a customer's size; only you know your cart. Supply
170
- `onAddToCart` and the dialog draws an **Add size X to cart** button on both its
171
- screens. Leave it out and there is no cart control at all.
168
+ `onAddToCart` and the dialog draws an **Add size X to cart** button under the
169
+ customer's size. Leave it out and there is no cart control at all.
170
+
171
+ When the customer is between two neighbouring sizes, the dialog says so ("Your
172
+ size: between M and L") and lets them pick which one goes in the cart: the
173
+ smaller for a closer fit, the larger for a looser one, with the recommended size
174
+ preselected. The button follows their choice, and any new recommendation (edited
175
+ measurements, another colour) resets it. Without `onAddToCart` there is nothing
176
+ to pick for, so the dialog shows the one recommended size and says the same
177
+ advice in words.
172
178
 
173
179
  It is called with one `intent` and must **resolve** (never reject) to an
174
180
  outcome:
175
181
 
176
182
  ```js
177
- onAddToCart: async ({ buttonId, size, sizeRun }) => {
178
- // buttonId — the containerId of the button this is about
179
- // size — the size the customer chose, e.g. "M"
180
- // sizeRun — every size the garment's chart offers, e.g. ["S", "M", "L"]
183
+ onAddToCart: async ({ buttonId, size, sizeRun, recommendedSize, recommendationId }) => {
184
+ // buttonId — the containerId of the button this is about
185
+ // size — the size to add: the customer's choice, e.g. "L"
186
+ // sizeRun — every size the garment's chart offers, e.g. ["S", "M", "L"]
187
+ // recommendedSize — the size the widget recommended, e.g. "M". It differs
188
+ // from `size` only when the customer picked the other of
189
+ // two neighbouring sizes
190
+ // recommendationId — the id of the recommendation on screen, or null
181
191
  const variant = resolveVariant(buttonId, size);
182
192
  if (!variant) return { ok: false, message: 'That size is not available in this colour.' };
183
193
  await addLineToCart(variant.id);
@@ -193,6 +203,13 @@ onAddToCart: async ({ buttonId, size, sizeRun }) => {
193
203
  | `quantity` | `ok: true` | Optional. How many of this line the cart now holds, for a repeat add |
194
204
  | `notice` | `ok: true` | Optional. A caveat when the add was adjusted on the way in (short stock, a cart limit) |
195
205
 
206
+ `recommendedSize` and `recommendationId` are new in this release, and only
207
+ added beside the fields you already read. A handler that reads `size` alone
208
+ keeps working and adds what the customer chose. Keep `recommendationId` with the
209
+ line item if you want to match a purchase to the recommendation behind it. The
210
+ widget records each recommendation in the background and nothing waits for
211
+ that record, so it may arrive after the add or not at all.
212
+
196
213
  Two rules the widget keeps for you: nothing is stated in the past tense until
197
214
  you have resolved `{ ok: true }`, and nothing reaches your handler without a
198
215
  click on the button — opening the dialog, a new result landing, and changing
@@ -213,7 +230,7 @@ It returns a handle for that button. Among its methods:
213
230
  | Method | Description |
214
231
  |--------|-------------|
215
232
  | `setGarment(garmentUrl)` | Point the button at another garment — a colour switch. The button's previous result is forgotten; a try-on still running for the previous garment is shown when it lands. |
216
- | `openSize()` | Open the dialog on the customer's size, exactly as the size on the button does — for a "check your size" control of your own. Does nothing while the button has no size to show. |
233
+ | `openSize()` | Open the dialog on the customer's size, exactly as the size on the button does — for a "check your size" control of your own. Before a try-on it shows the garment, the size and **Try on**. Does nothing while the button has no size to show. |
217
234
 
218
235
  > **Removed in 0.2.0:** `targetImageId`. It named an `<img>` on your page for the
219
236
  > widget to overwrite with the result, and it is gone — the widget shows every
@@ -327,7 +344,7 @@ The widget then contacts no one but you for its fonts, and a `font-src 'self'` p
327
344
 
328
345
  Your API keys are available in your **Souldi Dashboard**. The publishable key is scoped to the domains you whitelist — it cannot be used from unauthorized origins.
329
346
 
330
- > **Security note:** Authentication and AI generation go through your Souldi API endpoint and are handled server-side. The browser also reaches Souldi's own storage, through short-lived signed URLs the API issues: a photo upload goes straight to it, and result images come from Souldi's image CDN. Beyond Souldi, the browser contacts unpkg (the package's CDN) for two static assets: the fonts, unless you set [`fontsUrl`](#fonts), and the 3D mannequin on the size screen. No other third-party service is contacted.
347
+ > **Security note:** Authentication and AI generation go through your Souldi API endpoint and are handled server-side. The browser also reaches Souldi's own storage, through short-lived signed URLs the API issues: a photo upload goes straight to it, and result images come from Souldi's image CDN. Beyond Souldi, the browser contacts unpkg (the package's CDN) for its fonts, unless you set [`fontsUrl`](#fonts). No other third-party service is contacted.
331
348
 
332
349
  ---
333
350