@moveo-ai/web-client 0.112.0 → 0.113.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
@@ -219,8 +219,16 @@ payload listed below.
219
219
 
220
220
  `onAnalyticsEvent` receives every interaction the widget tracks, identified by
221
221
  the `event` field: `session_start`, `session_end`, `message_sent` (the user sent
222
- a message — never carries the text), `launcher_clicked`, `rating_submitted`, and
223
- the rest of the `AnalyticsEvent` enum in `src/hooks/useAnalytics.ts`.
222
+ a message — never carries the text), `launcher_clicked`,
223
+ `teaser_quick_reply_clicked` (a teaser reply the visitor tapped, carrying its
224
+ configured `label`), `teaser_composer_submitted` (the visitor sent their own
225
+ line from the teaser — carries no properties, because the text is theirs),
226
+ `rating_submitted`, and the rest of the `AnalyticsEvent` enum in
227
+ `src/hooks/useAnalytics.ts`.
228
+
229
+ No event carries message text. `properties` is forwarded verbatim by
230
+ integrations to their own analytics vendors, so anything the visitor typed stays
231
+ out of it — only copy the integration itself configured appears there.
224
232
 
225
233
  It also receives one event that is not an interaction: **`widget_loaded`**,
226
234
  raised as soon as the widget renders on the page. It carries no `session_id`,
@@ -324,6 +332,200 @@ MoveoAI.init({
324
332
  });
325
333
  ```
326
334
 
335
+ ### Teaser
336
+
337
+ The teaser is the invitation shown beside the closed launcher. `variant` picks
338
+ its shape, and each variant declares only the fields it renders, so a
339
+ placeholder on a variant with no input is a compile error rather than a silent
340
+ no-op.
341
+
342
+ | Variant | Needs | Renders |
343
+ | ---------------- | --------------- | ------------------------------------------------------------------- |
344
+ | `callout` | `message` | The line in the bubble. The default, and the teaser as it is today. |
345
+ | `question_chips` | `quick_replies` | Tappable questions **in place of** the line, at most three. |
346
+ | `composer` | `message` | The line with an input under it. |
347
+ | `none` | — | Launcher only. |
348
+
349
+ ```javascript
350
+ MoveoAI.init({
351
+ integrationId: 'YOUR_INTEGRATION_ID',
352
+ teaser: {
353
+ variant: 'question_chips',
354
+ quick_replies: ['Compare products', 'Help me choose'],
355
+ },
356
+ });
357
+
358
+ MoveoAI.init({
359
+ integrationId: 'YOUR_INTEGRATION_ID',
360
+ teaser: {
361
+ variant: 'composer',
362
+ message: 'Tell me what you sell and I will narrow it down.',
363
+ placeholder: 'e.g. a two-till bakery',
364
+ },
365
+ });
366
+ ```
367
+
368
+ With no `variant`, replies mean `question_chips` and their absence means
369
+ `callout`, so the shorthand `{ quick_replies: [...] }` keeps working.
370
+
371
+ #### A different teaser per page
372
+
373
+ `teaser.pages[]` varies the teaser by URL. The **first** entry whose `url`
374
+ appears in the page's URL wins, so specific paths belong above general ones, and
375
+ the `teaser` block itself covers everywhere else.
376
+
377
+ ```javascript
378
+ MoveoAI.init({
379
+ integrationId: 'YOUR_INTEGRATION_ID',
380
+ teaser: {
381
+ // The block is the teaser wherever no `pages[]` entry matches.
382
+ variant: 'callout',
383
+ message: 'Anything I can help with?',
384
+ pages: [
385
+ {
386
+ url: '/products/flex',
387
+ variant: 'question_chips',
388
+ quick_replies: ['Explain Flex to me', 'Compare Flex and Station Duo'],
389
+ },
390
+ {
391
+ url: '/shop',
392
+ variant: 'composer',
393
+ message: 'Nine systems here. Tell me what you sell.',
394
+ placeholder: 'e.g. a two-till bakery',
395
+ },
396
+ ],
397
+ },
398
+ });
399
+ ```
400
+
401
+ `url` is **a hint, not a pattern**: it matches when it appears anywhere in the
402
+ page's URL, compared case-insensitively. Nothing is escaped and nothing is
403
+ compiled, so a stray `.` or `(` cannot change what matches or throw. This is the
404
+ one thing `pages[]` does differently from the `page_triggers` it replaces, whose
405
+ keys were compiled with `new RegExp`.
406
+
407
+ `pages[]` is an array and not a map keyed by URL so that precedence is visible
408
+ to whoever wrote it. `variant: 'none'` on the block, with a `pages[]` entry that
409
+ opts back in, gives a teaser on those pages and nowhere else.
410
+
411
+ **It follows the visitor.** `clientUrl` is read once when the widget loads, so on
412
+ a single-page app it names the page the visitor arrived on for the rest of the
413
+ visit. When `pages[]` is configured the loader watches for navigation and relays
414
+ the new URL, and the teaser re-resolves. An integration that configures no
415
+ `pages[]` is not watched at all, because the watcher patches `history` and never
416
+ restores it.
417
+
418
+ **Chips replace the line, they do not sit under it.** Asking the question and
419
+ offering the answers says the same thing twice, and a bubble above
420
+ content-width chips leaves a ragged edge.
421
+
422
+ **Chips carry no dismiss.** `callout` and `composer` keep theirs, because both
423
+ cover part of the page. A chip is cheap to ignore, and a close control in the
424
+ stack reads as a fourth, broken chip. `close_opening_message_click` therefore
425
+ does not fire for `question_chips`.
426
+
427
+ **A variant degrades rather than rendering an empty surface.**
428
+ `question_chips` with no replies falls back to `callout`; `callout` or
429
+ `composer` with no line resolves to `none`.
430
+
431
+ #### Sending
432
+
433
+ A tapped chip and a typed composer line take the same path: the widget opens
434
+ and the text is sent as the visitor's first message, once the session is ready
435
+ and the visitor form, if any, is done. It replaces the configured
436
+ `welcome_trigger_message` for that conversation, so the visitor's own words
437
+ open it.
438
+
439
+ #### While the visitor is reading
440
+
441
+ A configured teaser steps out of the way as the host page scrolls, and fades
442
+ back a second after it stops. It is out of reach while it is out of sight, so a
443
+ keyboard cannot land on a chip nobody can see.
444
+
445
+ This applies only to an integration that sets a `teaser`. An integration with
446
+ nothing but a `welcome_message` keeps the opening message it has always had,
447
+ sitting still, and no scroll listener is attached to its pages.
448
+
449
+ #### When it appears, and when it gives up
450
+
451
+ Two optional clocks, both **off unless set**, so an integration that configures
452
+ neither behaves exactly as it does today — the teaser appears as soon as it has
453
+ something to say, and stays.
454
+
455
+ ```javascript
456
+ MoveoAI.init({
457
+ integrationId: 'YOUR_INTEGRATION_ID',
458
+ teaser: {
459
+ message: 'Anything I can help with?',
460
+ show_after: 8, // wait 8s before inviting
461
+ hide_after: 45, // retire after 45s on screen
462
+ },
463
+ });
464
+ ```
465
+
466
+ | Field | Unit | Off | Clamped to | Means |
467
+ | ------------ | ------- | --- | ---------- | ----------------------------------------------------- |
468
+ | `show_after` | seconds | `0` | 1–60 | Wait this long before the teaser appears. |
469
+ | `hide_after` | seconds | `0` | 5–300 | Retire it after this long, for the rest of the visit. |
470
+
471
+ Both are **block-level**, not per-page: they describe the visit, not the page. A
472
+ `pages[]` entry with its own delay would restart the clock on every navigation
473
+ of a single-page app, so the teaser would arrive later on each page and, past a
474
+ few, never at all.
475
+
476
+ `show_after` counts **settled** seconds, and any scroll starts it over: the
477
+ invitation waits for a calm moment rather than arriving in the middle of a
478
+ gesture. The trade is that a visitor who scrolls more often than the delay never
479
+ settles for long enough and never sees the teaser at all — pick the number
480
+ against how the page is actually read, because a long delay on a page people
481
+ scan continuously invites nobody.
482
+
483
+ It is counted from the moment the teaser has something to say, not from the page
484
+ loading — the config arrives over postMessage, so a clock started earlier would
485
+ come out shortest on exactly the slow pages a delay matters on. A
486
+ `showTeaserMessage()` call is never held behind it: the host asking for the
487
+ teaser means now.
488
+
489
+ `hide_after` counts the time the teaser is **on screen**. The clock pauses while
490
+ the visitor scrolls it out of the way and resumes with what is left, so a
491
+ visitor who spent the budget scrolling has not spent it ignoring the teaser.
492
+ Once retired, the teaser is gone for the visit; the launcher is not.
493
+
494
+ A malformed value — negative, non-numeric, infinite — reads as off rather than
495
+ being clamped up to the minimum, so a bad config cannot start hiding a teaser
496
+ the integration can see today.
497
+
498
+ #### The line
499
+
500
+ Precedence, highest first:
501
+
502
+ 1. A line handed to **`showTeaserMessage('...')`**, for as long as it is set.
503
+ Calling it with no argument clears the override and hands the teaser back to
504
+ the config.
505
+ 2. **`page_triggers[].opening_message`** for this URL.
506
+ 3. **`teaser.message`** (or the `pages[]` entry's).
507
+ 4. The deprecated top-level **`welcome_message`**.
508
+
509
+ `message` supersedes `welcome_message`, which is still read when `message` is
510
+ absent. The two legacy sources above it stay ahead of `teaser.message` on
511
+ purpose: per-page copy an integration already relies on, or a line the host
512
+ asked for by name, must not be replaced by a default someone adds later.
513
+
514
+ `page_triggers` is **deprecated and frozen**. It still varies the line and the
515
+ auto-sent opener by URL for the five integrations that set it, and it is not
516
+ removed, because one of them is a live customer. New work belongs on
517
+ `teaser.pages[]`.
518
+
519
+ `manual_teaser_mode` and `showTeaserMessage()` keep their meaning.
520
+
521
+ #### Restyling it
522
+
523
+ The teaser follows the accent out of the box. The bubble reads
524
+ `--color-opening-message-background` and
525
+ `--color-opening-message-foreground`, and the chips and the composer field
526
+ read the `--color-options-*` set. All five are in
527
+ [CSS Variables](#css-variables), and are set through `setCSSVariables()`.
528
+
327
529
  ### Inactivity Timeout
328
530
 
329
531
  The widget opens an inactivity modal once the visitor goes quiet, and asks
@@ -447,15 +649,21 @@ A colour that says "defaults to" follows `--color-accent` — or the colour name
447
649
  — until you set it. So an accent alone restyles the links, the rating, the send
448
650
  icon and the launcher, and `--color-chat-background` follows the background.
449
651
 
450
- Four more colours have no `theme_colors` key, so they are set through
652
+ Six more colours have no `theme_colors` key, so they are set through
451
653
  `setCSSVariables()` only:
452
654
 
453
- | Variable | Controls |
454
- | ---------------------------- | ---------------------------------------------------- |
455
- | `--color-chat-background` | The area behind the messages; follows the background |
456
- | `--color-options-background` | Quick reply button background |
457
- | `--color-options-foreground` | Quick reply button label; defaults to the accent |
458
- | `--color-options-border` | Quick reply button border; defaults to the accent |
655
+ | Variable | Controls |
656
+ | ------------------------------------ | ------------------------------------------------------------------- |
657
+ | `--color-chat-background` | The area behind the messages; follows the background |
658
+ | `--color-options-background` | Quick reply buttons, and the teaser chips and composer field |
659
+ | `--color-options-foreground` | Quick reply labels, the teaser composer text, and a chip under the pointer or the focus ring; defaults to the accent |
660
+ | `--color-options-border` | Quick reply borders, the teaser composer field, and a chip under the pointer or the focus ring; defaults to the accent |
661
+ | `--color-opening-message-background` | The teaser bubble, on `callout` and `composer`; defaults to the accent |
662
+ | `--color-opening-message-foreground` | The teaser bubble text; defaults to the visitor's own bubble text colour |
663
+
664
+ The two `opening-message` names are the teaser's, from before it was called
665
+ one. They are the only lever that moves the bubble off the accent, so an
666
+ integration whose accent is too dark to invite anybody sets them here.
459
667
 
460
668
  #### Renamed variables
461
669
 
@@ -1,6 +1,6 @@
1
1
  /*!
2
2
  *
3
- * @moveo-ai/web-client v0.112.0
3
+ * @moveo-ai/web-client v0.113.0
4
4
  * Copyright (c) Moveo.ai (TM)
5
5
  *
6
6
  */
@@ -1,6 +1,6 @@
1
1
  /*!
2
2
  *
3
- * @moveo-ai/web-client v0.112.0
3
+ * @moveo-ai/web-client v0.113.0
4
4
  * Copyright (c) Moveo.ai (TM)
5
5
  *
6
6
  */
@@ -1,6 +1,6 @@
1
1
  /*!
2
2
  *
3
- * @moveo-ai/web-client v0.112.0
3
+ * @moveo-ai/web-client v0.113.0
4
4
  * Copyright (c) Moveo.ai (TM)
5
5
  *
6
6
  */
@@ -1,6 +1,6 @@
1
1
  /*!
2
2
  *
3
- * @moveo-ai/web-client v0.112.0
3
+ * @moveo-ai/web-client v0.113.0
4
4
  * Copyright (c) Moveo.ai (TM)
5
5
  *
6
6
  */