@vio-live/web-sdk 0.23.0 → 0.24.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/README.md CHANGED
@@ -137,8 +137,13 @@ In order:
137
137
  carousel's id). `vio-slot-name` is its optional human label for the dashboard.
138
138
  2. Otherwise an **auto id**: `${scope}-${kind}-${ordinal + 1}` — `carousel-1`,
139
139
  `card-3`, `detail-1`, `article-123-carousel-1` — counted among the same kind of
140
- element **with the same scope**, in document order, when the element connects.
141
- Its label is `Carousel 1` (` · Title` when the scope has a label).
140
+ element **with the same scope**: on a fresh load elements connect in document
141
+ order, so the number is the document position; in a dynamic DOM a later element
142
+ takes the first free number and a removed element's number is reused — two live
143
+ elements never share an id. Its label is `Carousel 1` (` · Title` when the
144
+ scope has a label). Every id is capped at 100 characters (a long scope is cut,
145
+ the kind and number are kept), and the capped id is what gets declared,
146
+ exposed and sent.
142
147
 
143
148
  The attribute is **`vio-slot`** (property `vioSlot`, React prop `vioSlot`) — **not
144
149
  `slot`**, which is HTML's own attribute for shadow DOM slotting and stays untouched.
@@ -177,11 +182,16 @@ template that wants slots shared across articles simply sets no scope.
177
182
  Every mounted element registers its id (and label); after the first successful
178
183
  `Vio.bootstrap()` the SDK sends **one** manifest for the whole page
179
184
  (`POST /v2/mobile/components/manifest`, `mode: 'merge'`, so one page never
180
- deprecates another page's slots) and again whenever a new id or label appears. The
181
- same set is not re-sent from the same browser for **24 h** (remembered in
182
- `localStorage` under the platform and a hash of the key — never the key itself;
183
- without storage it simply posts each load). Best-effort, never throws. By hand,
184
- e.g. from an editor:
185
+ deprecates another page's slots) and again whenever a new id or label appears.
186
+ The browser remembers **each id — under its label and the campaign it was
187
+ declared with — for 24 h** in `localStorage` (under the platform and a hash of
188
+ the key — never the key itself; one state per id), so a multi-page site posts
189
+ only the ids a page adds, a revisit posts nothing, a renamed block or a page that
190
+ changed campaign posts again; without storage it simply posts each load. A failed
191
+ attempt (or a server that ignored `merge`) is not retried for 1 h; a declaration
192
+ sends at most 100 locations and a page load at most 3 manifests (each cap warns
193
+ once). Best-effort, never throws. By hand, e.g. from an editor (`force` re-sends
194
+ everything and ignores the per-load cap):
185
195
 
186
196
  ```ts
187
197
  await Vio.declareSlots(['hero', { id: 'sidebar', displayName: 'Sidebar' }], { force: true })
@@ -192,11 +202,62 @@ await Vio.declareSlots(['hero', { id: 'sidebar', displayName: 'Sidebar' }], { fo
192
202
 
193
203
  Among the active campaigns of the bootstrap, the one whose assignment names the
194
204
  slot (for this platform, or for all) and whose window is open right now; if
195
- several, the default campaign, else the first listed. No assignment → the default
205
+ several, the default campaign, else the first listed. No assignment → the page's
206
+ campaign (see *Page campaign*) when the bootstrap lists it as active → the default
196
207
  campaign. `Vio.campaignForSlot('hero')` answers from the last bootstrap (`null`
197
- before it); the pure `resolveSlotCampaign(boot, slot, now?, platform?)` is exported
198
- from `/core`. A carousel or card **without a `sponsor-id`** uses the primary sponsor
199
- of its slot's campaign; an explicit `sponsor-id` still wins.
208
+ before it); the pure `resolveSlotCampaign(boot, slot, now?, platform?, pageCampaignId?)`
209
+ is exported from `/core`. A carousel or card **without a `sponsor-id`** uses the primary sponsor
210
+ of its slot's campaign (the default campaign's when the slot has none), and the
211
+ detail opened by such a card does the same — before 0.23 a sponsorless card did
212
+ nothing. An explicit `sponsor-id` still wins.
213
+
214
+ ### Page campaign — an editorial page that belongs to one campaign
215
+
216
+ Native-SDK style placements stay the primary model: the **dashboard assigns**
217
+ a campaign to a slot. But an editorial page — a Vev article, a Replit article —
218
+ often *is* part of one campaign, and the page can say so:
219
+
220
+ ```ts
221
+ Vio.init({ apiKey: '…', campaignId: 12 }) // the whole page
222
+ ```
223
+
224
+ ```html
225
+ <body data-vio-campaign="12"> <!-- inheritable, like the scope -->
226
+ <vio-product-carousel vio-campaign="10" …> <!-- one element of another campaign -->
227
+ ```
228
+
229
+ Precedence per element: `vio-campaign` on the element → the nearest ancestor's
230
+ `data-vio-campaign` → `Vio.init({ campaignId })`; an unusable level (not a positive
231
+ integer, paused, unknown) falls through to the next. The attributes are read when
232
+ the element mounts or its own attribute changes — there is no observer on the
233
+ ancestors. On web/Replit a **per-article** campaign needs a per-article scope too
234
+ (`data-vio-scope`, see above): without it the auto ids of every article are the
235
+ same `carousel-1`, and the articles' campaigns would overwrite one another's
236
+ assignment; Vev ids are per block, so it does not apply there. What it does:
237
+
238
+ - **Declaration.** The manifest carries the page's campaign (top-level) and an
239
+ element's own campaign per location when it differs. The campaigns service
240
+ (campaigns micro PR #16) turns that into assignments of the page's slots
241
+ (`source: 'page'`) — **unless the dashboard already assigned the slot: the
242
+ dashboard wins**, and the SDK warns once per such slot (and once when this page
243
+ moved a slot from an earlier page assignment). A page that changes campaign
244
+ declares again; a campaign the micro did not take up is retried after an hour.
245
+ - **Resolution, immediately.** `Vio.campaignForSlot()` / `Vio.attribution()` (and
246
+ so the elements, the cart origins and the events) resolve as: the slot's
247
+ assignment (a dashboard one always; a page-sourced one unless the page now
248
+ declares a different active campaign or the known sponsor takes no part in it)
249
+ → the page's campaign, when the bootstrap lists it as active and — with a known
250
+ sponsor — that sponsor takes part in it → the sponsor's campaign → the default. An editor sees the effect in the Vev
251
+ editor before the manifest round-trips.
252
+ - `Vio.pageCampaign()` returns the init-level campaign as the bootstrap lists it
253
+ (active), else `null`.
254
+ - With a **known sponsor** (a card with `sponsor-id`), the page's campaign — and a
255
+ slot assignment the page made itself (`source: 'page'`) — count only when that
256
+ sponsor takes part in the campaign; otherwise the sponsor's own campaign applies.
257
+ A dashboard assignment counts unconditionally.
258
+ - `Vio.init({ debug: true })` logs the micro's `assignment` per slot at
259
+ `console.debug`. It is independent of `Vio.analytics.start({ debug })`, which
260
+ logs the analytics events.
200
261
 
201
262
  ## Attribution — the slot, the campaign, the article
202
263
 
@@ -205,26 +266,37 @@ and so do the Vev blocks and the Replit site:
205
266
 
206
267
  ```ts
207
268
  Vio.attribution('hero')
208
- // → { slot: 'hero', campaignId: 12, sponsorId: 7, contentId: 'https://host/sport/42', contentTitle: 'Why the lamp matters' }
269
+ // → { slot: 'hero', campaignId: 12, sponsorId: 7, contentId: 'article-42', contentTitle: 'Why the lamp matters' }
209
270
  ```
210
271
 
211
272
  `campaignId`/`sponsorId` are the slot's campaign and its primary sponsor (`{}` before
212
273
  the bootstrap); `contentId`/`contentTitle` are the **content** (the article) the page
213
274
  is. The place is the slot, the article is the content — both automatic.
214
275
 
215
- **Article (content) attribution.** `Vio.content()` resolves it once per page and
216
- every analytics event carries it as `content_id` / `content_title`: id from
217
- `Vio.init({ contentId })` → `?vio_content=` → `<meta name="vio:content-id">` →
276
+ **Article (content) attribution.** `Vio.content()` resolves it **live** (a few
277
+ `querySelector`s, so an SPA route change or a new title is seen on the next event)
278
+ and every analytics event carries it as `content_id` / `content_title`: id from
279
+ `Vio.init({ contentId })` → `?vio_content=` (honored by design — a page may be
280
+ told which article it is) → `<meta name="vio:content-id">` →
218
281
  `<link rel="canonical">` / `og:url` (without query and hash) → `location.pathname`;
219
282
  title from `Vio.init({ contentTitle })` → `?vio_content_title=` →
220
- `<meta name="vio:content-title">` → `og:title` → `document.title`. Per element,
283
+ `<meta name="vio:content-title">` → `og:title` → `document.title`. The collector
284
+ rejects a whole event over an id of 128 chars or a title of 255, so a candidate id
285
+ that does not fit is skipped for the next one (and omitted when none fits) and
286
+ titles are cut — in one place, before anything is sent. Per element,
221
287
  `vio-content-id` / `vio-content-title` override it (a teaser for another article).
222
288
 
223
289
  **What analytics gets.** Every event an element emits (`vio:product-click`,
224
290
  `vio:product-add`, `vio:added-to-cart`, `vio:checkout-open`, …) and every event it
225
291
  tracks carries `slot` (the effective id), `location_id` (= the slot), `content_id`
226
- and `content_title`. `campaign_id` resolves as explicit `campaignId` → the slot's
227
- assigned campaign → the sponsor's campaign → the default.
292
+ and `content_title`. `campaign_id` resolves as explicit `campaignId` (the element's
293
+ own attribution travels in its event detail and is honored as such) → the slot's
294
+ assigned campaign → the page's campaign (when the sponsor takes part in it) → the
295
+ sponsor's campaign → the default. Two things changed from
296
+ 0.22 for carousels: a carousel's impressions and `view_item_list` report
297
+ `location_id` as its slot id (`carousel-1`, or the `vio-slot`) instead of its
298
+ `label`, and every event now carries `content_id` / `content_title` — including
299
+ the single-campaign `begin_checkout` / `purchase`.
228
300
 
229
301
  ### Cart origins and the purchase per campaign
230
302
 
@@ -246,9 +318,21 @@ They persist with the cart; carts from before origins read as one empty origin.
246
318
  At checkout and purchase the cart is **split per campaign**: `begin_checkout` and
247
319
  `purchase` go out **once per campaign in the cart**, each with that campaign's
248
320
  items (quantities from the origins; units without one fall into the campaign the
249
- current rule resolves), its own `value`, the same `order_id`, and the slice's
250
- `slot` / `content_id` when they are unique in it. A cart with a single campaign
251
- behaves exactly as before: one event.
321
+ current rule resolves), its own `value` (two decimals), the same `order_id`, the
322
+ slice's `slot` / `content_id` only when they are unique in it, and
323
+ `props: { slice, slices }` so the collector can count the order once. A cart with a
324
+ single campaign is one event, as before; when its origins name the campaign, the
325
+ event names it too (plus the unique slot / content). A cart without origins
326
+ (0.22) reports exactly as it did.
327
+
328
+ The split is public, so a host with its own analytics sink (Vev) applies the same
329
+ rule instead of its own:
330
+
331
+ ```ts
332
+ Vio.cartSlices() // over the live cart; Vio.cartSlices(sponsorId) for one sponsor
333
+ Vio.cartSlices(undefined, { items }) // pure, over a snapshot of lines
334
+ // → CartSlice[]: { campaignId?, sponsorId?, slot?, contentId?, items: VioItem[], value, currency?, slice, slices }
335
+ ```
252
336
 
253
337
  ## Theming
254
338
 
@@ -0,0 +1,3 @@
1
+
2
+ //# sourceMappingURL=chunk-45YKS5UG.js.map
3
+ //# sourceMappingURL=chunk-45YKS5UG.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":[],"names":[],"mappings":"","file":"chunk-25QECQDS.js"}
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"chunk-45YKS5UG.js"}