@thg-agentic/hyper-personalisation-web 3.31.0 → 3.34.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 +71 -11
- package/dist/hp.min.js +3 -3
- package/dist/onsite-answers.min.js +2 -2
- package/dist/onsite-products.min.js +2 -2
- package/dist/onsite-render.min.js +2 -2
- package/dist/onsite-values.min.js +1 -0
- package/dist/onsite-wheel.min.js +1 -1
- package/dist/onsite.min.js +1 -1
- package/package.json +3 -2
- package/src/index.js +38 -1
- package/src/onsite/answers.js +6 -3
- package/src/onsite/api.js +5 -2
- package/src/onsite/countdown.js +21 -11
- package/src/onsite/index.js +100 -23
- package/src/onsite/money.js +11 -0
- package/src/onsite/page-facts.js +61 -0
- package/src/onsite/preview.js +27 -12
- package/src/onsite/products.js +3 -9
- package/src/onsite/render.js +14 -9
- package/src/onsite/rich-text.js +12 -1
- package/src/onsite/rules.js +47 -8
- package/src/onsite/values-loader.js +6 -0
- package/src/onsite/values.js +218 -0
- package/src/onsite-facade.js +57 -2
- package/src/session/record.js +18 -0
- package/src/session/signals.js +4 -0
- package/src/version.js +1 -1
- package/types/onsite-preview.d.ts +40 -0
- package/types/onsite.d.ts +73 -3
package/README.md
CHANGED
|
@@ -516,8 +516,9 @@ itself: `import { startOnsite } from '@thg-agentic/hyper-personalisation-web/ons
|
|
|
516
516
|
`startOnsite(client, options)`. On the CDN, `hp.min.js` loads `onsite.min.js` (and that loads
|
|
517
517
|
`onsite-render.min.js` once a trigger fires, `onsite-wheel.min.js` for a wheel,
|
|
518
518
|
`onsite-answers.min.js` for a date or number question, `onsite-products.min.js` for a products
|
|
519
|
-
block, `onsite-audiences.min.js` for a form limited to audiences
|
|
520
|
-
|
|
519
|
+
block, `onsite-audiences.min.js` for a form limited to audiences, `onsite-values.min.js` for a form
|
|
520
|
+
whose text has shopper values) from beside itself; a self-hosted copy keeps all seven beside
|
|
521
|
+
`hp.min.js`. The runtime watches triggers, caps and consent and asks for the
|
|
521
522
|
decision; the renderer and its stylesheet are fetched only when a trigger fires, alongside the
|
|
522
523
|
decision request, so a page whose forms never fire never downloads them. If the renderer cannot
|
|
523
524
|
load, the form is not shown (`hp.onsite.debug()` says `the renderer did not load`) and the page goes
|
|
@@ -570,7 +571,8 @@ on desktop arms after `delay_ms` (5 s) and once the pointer has been below the t
|
|
|
570
571
|
viewport, and fires when it leaves through the top edge, moving up. On mobile it is a fast upward
|
|
571
572
|
flick (120px at 0.5 px/ms) after 30% of the page, or a return to the tab after 10 s away. Pages
|
|
572
573
|
(include/exclude globs on the path, `*` matching anything, in any case), devices (`mobile` is a coarse pointer
|
|
573
|
-
or under 768px), the campaign the visit arrived from (`arrived_from`),
|
|
574
|
+
or under 768px), the campaign the visit arrived from (`arrived_from`), what the page says
|
|
575
|
+
(`page_facts`), dates and caps are checked
|
|
574
576
|
first; then `POST /v1/decisions` is asked for that one
|
|
575
577
|
slot. A held-out visitor, or no decision, sees nothing. One overlay per page view: of the forms that
|
|
576
578
|
fire within 50 ms, the highest `priority`; once one has shown (or the visitor was held out of it),
|
|
@@ -587,17 +589,50 @@ property's forms, `thg_hp_answers:<property>` in localStorage: the latest answer
|
|
|
587
589
|
question (its key and the option values chosen; never a date, a number, typed text or contact
|
|
588
590
|
data), each for 90 days from when it was given, written from a quiz's `Preferences Stated` (so only with analytics consent too) and a
|
|
589
591
|
sign-up form's answers once the gateway took them. A form limited to
|
|
590
|
-
an early audience, one whose every rule is
|
|
591
|
-
answers with the visit, so it shows on the next
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
592
|
+
an early audience, one whose every rule is about the visit or an answer the shopper gave (is one
|
|
593
|
+
of, includes all of), is then decided from those answers with the visit, so it shows on the next
|
|
594
|
+
page view without waiting for the warehouse. An audience with an "is none of" answer is never
|
|
595
|
+
early: a device that keeps no answer cannot tell, so the warehouse decides it. The datafile ships
|
|
596
|
+
an early audience as a MIXED audience (`rules` its visit rules) with `early: true` and its
|
|
597
|
+
`answer_rules`: `answer.<question>` with `in` (is one of) or `all` (includes all of) and a list of
|
|
598
|
+
answers. A datafile written before 2026-11 may still carry `not_in` (is none of), which holds with
|
|
599
|
+
no answer kept. The runtime sends the early audiences
|
|
595
600
|
that hold on the kept answers as `answered_audiences`. The answers go when personalisation is
|
|
596
601
|
refused or withdrawn, at a sign-out and when another user signs in. Anywhere else (the page's own
|
|
597
602
|
`./session`, which carries no answers, an answer given on another device, an earlier SDK) an early
|
|
598
603
|
audience is MIXED: met where the warehouse qualified the shopper on its answers and its visit rules
|
|
599
604
|
hold, from the next profile update.
|
|
600
605
|
|
|
606
|
+
**Shopper values in text (3.32).** A heading or paragraph may hold a value node,
|
|
607
|
+
`{ v, fallback, format? }`, filled in the browser as a text node (never markup): trimmed, without
|
|
608
|
+
control or invisible characters, at most 60 characters with an ellipsis. `v` is one of
|
|
609
|
+
`basket_value` (the basket this visit, as a basket bar reads it, as money in the `Cart Updated`'s
|
|
610
|
+
own `currency`, else the client's), `basket_items` (the items its lines hold: each line's whole
|
|
611
|
+
`quantity`, a line without one or with a fractional one once, zero or less none), `first_name` (what the page gave, below) or `answer.<question>` (the label of the
|
|
612
|
+
shopper's answer in the form's language: one given in this form, else the one kept under
|
|
613
|
+
personalisation consent; none without it). `format.style` is `number`, `money`, `date`,
|
|
614
|
+
`relative` or `text`, formatted with `Intl` in the form's locale, and `format.plural`
|
|
615
|
+
(`{ one, other }`) follows a number with its word ("3 items"). A value the page does not know shows
|
|
616
|
+
its `fallback`; with the block's `missing: "hide"` the block is not shown instead. The page gives
|
|
617
|
+
what only it knows with `hp.onsite.values({ first_name: 'Alex' })` (or `startOnsite`'s `values`):
|
|
618
|
+
kept in the page's memory only, drawn as text, never sent, stored or logged; each call replaces the
|
|
619
|
+
last, `values({})` forgets it. The formatting is its own chunk (`onsite-values.min.js`), loaded
|
|
620
|
+
only for a form whose text has a value; if it does not load, the form is not shown
|
|
621
|
+
(`values-failed`). The public API serves such a form only to 3.32 or later.
|
|
622
|
+
|
|
623
|
+
**Daily cut-off.** A form's `window.daily` (3.33 or later) is a cut-off that comes back each day, such
|
|
624
|
+
as "order by 1 AM for next-day delivery". The server works out when it falls, in the shop's time
|
|
625
|
+
zone on the days it is set for, and the settings list those moments: `next`, epoch milliseconds,
|
|
626
|
+
earliest first, for the next 8 days. So the browser compares numbers and carries no time-zone code,
|
|
627
|
+
and every shopper counts to the same moment, whatever their own clock. It is targeting, checked with
|
|
628
|
+
the form's pages and dates before any decision is asked for, so held-out and shown visitors are
|
|
629
|
+
filtered alike: the form is not eligible while none of the moments is still ahead (`no cut-off
|
|
630
|
+
ahead`) or, with `within_hours`, while the next is further away than that (`outside its daily
|
|
631
|
+
window`). A countdown with `daily: true`, on any step and as many as the form has, counts to the
|
|
632
|
+
first ahead: at 23:00 a 01:00 cut-off is two hours away, and as one passes the countdown moves on to
|
|
633
|
+
the next. A preview shows the form whatever the time. The public API serves a form with a daily
|
|
634
|
+
cut-off, or a daily countdown, only to 3.33 or later.
|
|
635
|
+
|
|
601
636
|
**Arrived from.** A form with `arrived_from` shows only to a visit that landed from a matching
|
|
602
637
|
campaign: `[{ field, match, values }]`, `field` one of `source`, `medium`, `campaign`, `content`,
|
|
603
638
|
`term` (the link's `utm_*` tags), `match` `is` or `contains`, compared trimmed and in any case.
|
|
@@ -617,6 +652,27 @@ form unless the rules hold, so an SDK older than 3.26 never shows one. A preview
|
|
|
617
652
|
URL's tags: the console's Preview on site link adds them. Click ids
|
|
618
653
|
(`fbclid`, `gclid`) are not a campaign.
|
|
619
654
|
|
|
655
|
+
**Page facts (3.34).** A form with `page_facts` shows only on a page that says so:
|
|
656
|
+
`[{ fact, op, values }]`, `op` one of `is`, `is_not` (any of `values` meets it, or none does),
|
|
657
|
+
`at_least` and `at_most` (one number). The page gives its facts with
|
|
658
|
+
`hp.onsite.facts({ next_day_eligible: true, delivery_tier: 'express' })`: keys of lower-case
|
|
659
|
+
letters, digits and `_` (up to 40), values `true`, `false`, a number or text (up to 100
|
|
660
|
+
characters), at most 20. Each call replaces the last; `facts({})` forgets them. They hold for the
|
|
661
|
+
page path they were given on only, so a single-page app's next page starts with none, and are never
|
|
662
|
+
stored. The rules are judged when the form's trigger fires, so facts the page gives after it loads
|
|
663
|
+
(or again for another variant) count for a form that waits on one (exit intent, a delay, a scroll,
|
|
664
|
+
time or idle). An inline form, or one that shows on arrival with no delay, is judged as onsite
|
|
665
|
+
starts: give its facts before then (before `startOnsite`, or as the page renders). A preview shows
|
|
666
|
+
whatever the page says. Every rule must hold, a fact the page did not give meets no rule, and a rule this SDK cannot
|
|
667
|
+
read never matches (`not eligible: the page does not say so`). The decision request for such a form
|
|
668
|
+
carries the facts as `page_facts` (never logged), and the public API answers no form unless the
|
|
669
|
+
rules hold, so an SDK older than 3.34 never shows one.
|
|
670
|
+
|
|
671
|
+
**Products.** A form whose settings carry `products: true` shows only on the page of a chosen
|
|
672
|
+
product: the request carries the page's product, the `product_id` of the last `Product Viewed` on
|
|
673
|
+
this page, as `visit_context.product_id`, and the public API checks it against the form's product
|
|
674
|
+
filters in the catalogue. A page with no product does not ask (`not eligible: not a product page`).
|
|
675
|
+
|
|
620
676
|
| Option | |
|
|
621
677
|
|---|---|
|
|
622
678
|
| `audiences()` | The visitor's session audiences, when the page evaluates them: a form targeting none of them is skipped. Without it HP's server decides, from the visitor's stored audiences and the in-visit audiences whose rules hold in this visit (the runtime evaluates them from `GET /v1/audiences/datafile` and sends them with the decision). |
|
|
@@ -624,6 +680,7 @@ URL's tags: the console's Preview on site link adds them. Click ids
|
|
|
624
680
|
| `servingHost`, `gatewayHost` | Where decisions and claims go (default: the host the SDK was pointed at) and capture goes (default: where events go). |
|
|
625
681
|
| `debug` | Log why each form did or did not show. Also on with `?hp_debug=onsite`. |
|
|
626
682
|
| `settings` | Development and tests: these forms instead of the settings'. |
|
|
683
|
+
| `values` | What the page knows of the shopper for a form's text, `{ first_name }`, as `hp.onsite.values` (3.32). |
|
|
627
684
|
|
|
628
685
|
`hp.onsite.debug()` returns each form's state and reasons (`armed`, `not eligible: per_session cap`,
|
|
629
686
|
`held out`, `shown`, …), and since 3.29 a `code`, which stays the same across releases while the
|
|
@@ -635,9 +692,10 @@ on it, not on the reasons. `code` is always present, so a trace built by hand ne
|
|
|
635
692
|
| `hp.onsite` | |
|
|
636
693
|
|---|---|
|
|
637
694
|
| `open(name)`, `show(name)` | Show a form now, past its trigger; resolves with whether it showed. `name` is a form's slot, else one of a form's `aliases` (the settings' names for it: `footer_signup` for the site's own sign-up button), matched exactly; a slot wins over an alias. A `manual` form, or one opened by an alias, shows each time (see above); any other keeps to its caps. From there it is that form, by its slot, in every event. |
|
|
638
|
-
| `on(name, fn)` | `shown`, `submitted`, `apply`, `closed`, `held` or `nothing`, with `{ slot, arm, format }` (`nothing`: `{ slot, format, code }`); `submitted` and `apply` add `coupon` (`{ code, prize }` or `null`), `closed` adds `reason`. `held` is a held-out visitor whose trigger fired (a form the site opened says so by `open` resolving `false` instead), so a site replacing its own popup with HP's shows its own there and the holdout compares the two. `nothing` (since 3.29) is a covering form (any but `inline`) whose own trigger fired and for which HP will show nothing on this page view; the site shows its own there too. `code` says why: `decision-failed` (a network error, any status but 200, a 200 without a list of decisions, or no answer within 3 s), `no-creative`, `no-code` (a reminder with no code to remind of), `renderer-failed` or `
|
|
695
|
+
| `on(name, fn)` | `shown`, `submitted`, `apply`, `closed`, `held` or `nothing`, with `{ slot, arm, format }` (`nothing`: `{ slot, format, code }`); `submitted` and `apply` add `coupon` (`{ code, prize }` or `null`), `closed` adds `reason`. `held` is a held-out visitor whose trigger fired (a form the site opened says so by `open` resolving `false` instead), so a site replacing its own popup with HP's shows its own there and the holdout compares the two. `nothing` (since 3.29) is a covering form (any but `inline`) whose own trigger fired and for which HP will show nothing on this page view; the site shows its own there too. `code` says why: `decision-failed` (a network error, any status but 200, a 200 without a list of decisions, or no answer within 3 s), `no-creative`, `no-code` (a reminder with no code to remind of), `renderer-failed`, `products-failed` or `values-failed` (the chunk did not load within 3 s), or `error` (the form failed while it was being opened). It comes at most 3.05 s after the trigger: the 50 ms in which forms that fire together compete, then one 3 s deadline they share; an answer after it is never shown, so the site needs no timer of its own. It is not said when another covering form that fired with it shows, or the visitor is held out (`held` is said instead); for a form the site opened (`open` resolves `false`); on a suppressed page (`suppress`: the site is keeping its own screen clear); when its caps, the gap between popups, the consent banner or a form already on screen keep it away; or for an empty basket, which is tried again when the basket changes. Of the forms that fire together, only the highest HP has nothing for says it: the slot whose place the site takes. Like a holdout, it spends the page view's allowance, so no other HP popup, sheet, bar or teaser follows it on that page view (a later trigger is `allowance-spent`; a form the shopper asks for, `manual` or by an alias, still opens). A form with prizes says `submitted` once, when its claim settles: with the code, or `null` if the claim failed. Returns a function that stops listening. A throwing listener is logged, never breaks the form. |
|
|
639
696
|
| `ready()` | Resolves once the page knows whether onsite forms run on it: each form's state, as `debug()`, once the runtime has started, or `[]` once none will (not opted in, no live form, the runtime failed to load). A site keeping its own popup where HP has none for the visitor reads it. |
|
|
640
697
|
| `suppress(reason)`, `unsuppress(reason?)` | Take any form off the screen quietly (not a dismissal) and keep forms away while any reason holds; `unsuppress()` lifts all. |
|
|
698
|
+
| `values(given)` | What the page knows of the shopper for a form's text, `{ first_name }` (3.32): in memory only, never sent; each call replaces the last. |
|
|
641
699
|
|
|
642
700
|
**Formats.** A `teaser` is a tab (its first step) that opens the rest as a popup; the tab is kept on
|
|
643
701
|
the session's later pages until opened or dismissed, and never traps focus. A `bar` with a
|
|
@@ -651,7 +709,8 @@ image or muted, looping video media; see ADR 0064.
|
|
|
651
709
|
**Preview.** `?hp_preview=<token>` asks `POST /v1/decisions` for the previewed version and counts
|
|
652
710
|
nothing. For an editor, `import { mountPreview } from
|
|
653
711
|
'@thg-agentic/hyper-personalisation-web/onsite/preview'`; `mountPreview(host, creative, { step,
|
|
654
|
-
device: 'desktop' | 'mobile', consentText, smsConsentText, questions, prizes, basketValue })`
|
|
712
|
+
device: 'desktop' | 'mobile', consentText, smsConsentText, questions, prizes, basketValue, values })`
|
|
713
|
+
draws the creative (with `values`, a returning shopper's sample values; without, every fallback)
|
|
655
714
|
inside `host` with no request, event or counter, and returns `{ update(creative, options), destroy(),
|
|
656
715
|
step() }`. It is not part of the runtime chunk.
|
|
657
716
|
|
|
@@ -714,7 +773,8 @@ each form did or did not show, and a preview link (`?hp_preview=`) counts nothin
|
|
|
714
773
|
(about 15.7 KiB), so a page that shows a form loads about 22.3 KiB of onsite code and one whose
|
|
715
774
|
forms never fire about 6.6 KiB; `onsite-wheel.min.js` under 8 KiB (about 1.8 KiB),
|
|
716
775
|
`onsite-answers.min.js` under 2 KiB (about 1.5 KiB), `onsite-products.min.js` under 2.6 KiB (about
|
|
717
|
-
2.5 KiB), `onsite-audiences.min.js` under 1.63 KiB (about 1.61 KiB).
|
|
776
|
+
2.5 KiB), `onsite-audiences.min.js` under 1.63 KiB (about 1.61 KiB), `onsite-values.min.js` under
|
|
777
|
+
1.5 KiB (about 1.35 KiB). The core carries only the loader.
|
|
718
778
|
|
|
719
779
|
**Products in a form.** A creative's `products` block (ADR 0064 as amended 2026-11-02) is filled by
|
|
720
780
|
the public API in the decision that serves the form, as the block's `served`. For a form whose
|