@syntrologie/runtime-sdk 2.1.0-canary.8 → 2.1.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.
Files changed (159) hide show
  1. package/CAPABILITIES.md +415 -115
  2. package/dist/SmartCanvasApp.d.ts +1 -3
  3. package/dist/SmartCanvasApp.js +16 -10
  4. package/dist/SmartCanvasApp.js.map +1 -1
  5. package/dist/SmartCanvasElement.d.ts +0 -2
  6. package/dist/SmartCanvasElement.js +3 -8
  7. package/dist/SmartCanvasElement.js.map +1 -1
  8. package/dist/actions/executors/index.js +4 -1
  9. package/dist/actions/executors/index.js.map +1 -1
  10. package/dist/actions/executors/tour.js +2 -2
  11. package/dist/actions/executors/tour.js.map +1 -1
  12. package/dist/actions/validation.js +5 -30
  13. package/dist/actions/validation.js.map +1 -1
  14. package/dist/adaptives/adaptive-chatbot/index.js +7 -5
  15. package/dist/adaptives/adaptive-chatbot/index.js.map +4 -4
  16. package/dist/adaptives/adaptive-content/index.js +22 -0
  17. package/dist/adaptives/adaptive-content/index.js.map +7 -0
  18. package/dist/adaptives/adaptive-faq/index.js +11 -0
  19. package/dist/adaptives/adaptive-faq/index.js.map +7 -0
  20. package/dist/adaptives/adaptive-gamification/index.js +2 -0
  21. package/dist/adaptives/adaptive-gamification/index.js.map +7 -0
  22. package/dist/adaptives/adaptive-nav/index.js +12 -0
  23. package/dist/adaptives/adaptive-nav/index.js.map +7 -0
  24. package/dist/adaptives/adaptive-overlays/index.js +94 -0
  25. package/dist/adaptives/adaptive-overlays/index.js.map +7 -0
  26. package/dist/api.d.ts +1 -9
  27. package/dist/api.js +5 -5
  28. package/dist/api.js.map +1 -1
  29. package/dist/apps/AppLoader.d.ts +5 -0
  30. package/dist/apps/AppLoader.js +28 -3
  31. package/dist/apps/AppLoader.js.map +1 -1
  32. package/dist/apps/index.d.ts +1 -1
  33. package/dist/apps/types.d.ts +26 -0
  34. package/dist/blocks/data/ComparisonBlock.js +12 -9
  35. package/dist/blocks/data/ComparisonBlock.js.map +1 -1
  36. package/dist/blocks/data/StatsBlock.js +11 -10
  37. package/dist/blocks/data/StatsBlock.js.map +1 -1
  38. package/dist/blocks/index.d.ts +9 -5
  39. package/dist/blocks/index.js +2 -2
  40. package/dist/blocks/index.js.map +1 -1
  41. package/dist/blocks/interactive/ChecklistBlock.js +13 -12
  42. package/dist/blocks/interactive/ChecklistBlock.js.map +1 -1
  43. package/dist/blocks/interactive/RatingBlock.js +18 -12
  44. package/dist/blocks/interactive/RatingBlock.js.map +1 -1
  45. package/dist/blocks/notification/NotificationBlock.js +24 -23
  46. package/dist/blocks/notification/NotificationBlock.js.map +1 -1
  47. package/dist/blocks/theme-tokens.d.ts +23 -0
  48. package/dist/blocks/theme-tokens.js +25 -0
  49. package/dist/blocks/theme-tokens.js.map +1 -0
  50. package/dist/bootstrap.js +21 -24
  51. package/dist/bootstrap.js.map +1 -1
  52. package/dist/components/ShadowCanvasOverlay.d.ts +6 -3
  53. package/dist/components/ShadowCanvasOverlay.js +191 -115
  54. package/dist/components/ShadowCanvasOverlay.js.map +1 -1
  55. package/dist/components/TileCard.d.ts +1 -5
  56. package/dist/components/TileCard.js +62 -286
  57. package/dist/components/TileCard.js.map +1 -1
  58. package/dist/components/TileWheel.js +24 -4
  59. package/dist/components/TileWheel.js.map +1 -1
  60. package/dist/configFetcher.d.ts +5 -1
  61. package/dist/configFetcher.js +33 -2
  62. package/dist/configFetcher.js.map +1 -1
  63. package/dist/context/ContextManager.js +3 -2
  64. package/dist/context/ContextManager.js.map +1 -1
  65. package/dist/decisions/engine.d.ts +4 -0
  66. package/dist/decisions/engine.js +6 -1
  67. package/dist/decisions/engine.js.map +1 -1
  68. package/dist/decisions/schema.d.ts +241 -0
  69. package/dist/decisions/schema.js +8 -0
  70. package/dist/decisions/schema.js.map +1 -1
  71. package/dist/decisions/strategies/rules.js +14 -0
  72. package/dist/decisions/strategies/rules.js.map +1 -1
  73. package/dist/decisions/types.d.ts +21 -1
  74. package/dist/editorLoader.d.ts +19 -9
  75. package/dist/editorLoader.js +112 -94
  76. package/dist/editorLoader.js.map +1 -1
  77. package/dist/events/EventAccumulator.d.ts +29 -0
  78. package/dist/events/EventAccumulator.js +101 -0
  79. package/dist/events/EventAccumulator.js.map +1 -0
  80. package/dist/events/index.d.ts +2 -0
  81. package/dist/events/index.js +1 -0
  82. package/dist/events/index.js.map +1 -1
  83. package/dist/events/normalizers/posthog.js +12 -4
  84. package/dist/events/normalizers/posthog.js.map +1 -1
  85. package/dist/events/types.d.ts +4 -0
  86. package/dist/events/types.js +5 -0
  87. package/dist/events/types.js.map +1 -1
  88. package/dist/experiments/adapters/growthbook.d.ts +2 -1
  89. package/dist/experiments/adapters/growthbook.js +11 -23
  90. package/dist/experiments/adapters/growthbook.js.map +1 -1
  91. package/dist/experiments/types.d.ts +5 -0
  92. package/dist/fetchers/experimentsFetcher.d.ts +23 -1
  93. package/dist/fetchers/experimentsFetcher.js +48 -1
  94. package/dist/fetchers/experimentsFetcher.js.map +1 -1
  95. package/dist/fetchers/mergeConfigs.d.ts +29 -0
  96. package/dist/fetchers/mergeConfigs.js +38 -0
  97. package/dist/fetchers/mergeConfigs.js.map +1 -0
  98. package/dist/hooks/useShadowCanvasConfig.js +1 -1
  99. package/dist/hooks/useShadowCanvasConfig.js.map +1 -1
  100. package/dist/index.d.ts +1 -0
  101. package/dist/index.js +28 -0
  102. package/dist/index.js.map +1 -1
  103. package/dist/notifications/NotificationToastStack.d.ts +8 -0
  104. package/dist/notifications/NotificationToastStack.js +118 -0
  105. package/dist/notifications/NotificationToastStack.js.map +1 -0
  106. package/dist/notifications/index.d.ts +9 -0
  107. package/dist/notifications/index.js +6 -0
  108. package/dist/notifications/index.js.map +1 -0
  109. package/dist/notifications/matcher.d.ts +25 -0
  110. package/dist/notifications/matcher.js +66 -0
  111. package/dist/notifications/matcher.js.map +1 -0
  112. package/dist/notifications/types.d.ts +64 -0
  113. package/dist/notifications/types.js +13 -0
  114. package/dist/notifications/types.js.map +1 -0
  115. package/dist/notifications/useNotifications.d.ts +19 -0
  116. package/dist/notifications/useNotifications.js +104 -0
  117. package/dist/notifications/useNotifications.js.map +1 -0
  118. package/dist/notifications/useNotifyWatcher.d.ts +15 -0
  119. package/dist/notifications/useNotifyWatcher.js +62 -0
  120. package/dist/notifications/useNotifyWatcher.js.map +1 -0
  121. package/dist/overlays/runtime/overlay/runner.js +2 -2
  122. package/dist/overlays/runtime/overlay/runner.js.map +1 -1
  123. package/dist/render/RenderContext.js +5 -3
  124. package/dist/render/RenderContext.js.map +1 -1
  125. package/dist/render/types.d.ts +2 -3
  126. package/dist/runtime.d.ts +3 -0
  127. package/dist/runtime.js +28 -12
  128. package/dist/runtime.js.map +1 -1
  129. package/dist/smart-canvas.esm.js +131 -102
  130. package/dist/smart-canvas.esm.js.map +4 -4
  131. package/dist/smart-canvas.js +40870 -43700
  132. package/dist/smart-canvas.js.map +4 -4
  133. package/dist/smart-canvas.min.js +131 -102
  134. package/dist/smart-canvas.min.js.map +4 -4
  135. package/dist/theme/defaultTheme.d.ts +7 -3
  136. package/dist/theme/defaultTheme.js +78 -51
  137. package/dist/theme/defaultTheme.js.map +1 -1
  138. package/dist/theme/index.d.ts +1 -1
  139. package/dist/theme/index.js +1 -1
  140. package/dist/theme/index.js.map +1 -1
  141. package/dist/theme/types.d.ts +7 -0
  142. package/dist/types.d.ts +13 -100
  143. package/dist/types.js.map +1 -1
  144. package/dist/version.d.ts +1 -1
  145. package/dist/version.js +1 -1
  146. package/dist/version.js.map +1 -1
  147. package/dist/widgets/WidgetRegistry.d.ts +6 -1
  148. package/dist/widgets/WidgetRegistry.js +12 -4
  149. package/dist/widgets/WidgetRegistry.js.map +1 -1
  150. package/package.json +18 -12
  151. package/schema/canvas-config.schema.json +34 -69
  152. package/schema/event-catalog.schema.json +157 -0
  153. package/scripts/validate-config.mjs +20 -13
  154. package/dist/adaptives/faq/index.js +0 -11
  155. package/dist/adaptives/faq/index.js.map +0 -7
  156. package/dist/adaptives/gamification/index.js +0 -2
  157. package/dist/adaptives/gamification/index.js.map +0 -7
  158. package/dist/adaptives/nav/index.js +0 -11
  159. package/dist/adaptives/nav/index.js.map +0 -7
package/CAPABILITIES.md CHANGED
@@ -291,7 +291,7 @@ Removes a CSS class from an element.
291
291
 
292
292
  # @syntrologie/adapt-faq
293
293
 
294
- FAQ accordion widget with conditional item visibility.
294
+ Collapsible Q&A accordion with actions, rich content, feedback, and personalization. Supports mounting a full FAQ widget, scrolling to specific items, toggling item state, and dynamically updating the item list at runtime.
295
295
 
296
296
  ## Actions
297
297
 
@@ -299,22 +299,33 @@ FAQ accordion widget with conditional item visibility.
299
299
 
300
300
  Mounts an FAQ accordion widget to a surface slot.
301
301
 
302
- | Property | Type | Required | Description |
303
- | -------------- | ------------- | -------- | -------------------------------------------------------- |
304
- | `kind` | `"mount_faq"` | Yes | Action type |
305
- | `slot` | string | Yes | Target slot (e.g., `"drawer_right"`, `"overlay_center"`) |
306
- | `config.title` | string | No | Widget title |
307
- | `config.items` | array | Yes | FAQ items (see below) |
302
+ | Property | Type | Required | Description |
303
+ | ----------------------- | --------------------------------- | -------- | ------------------------------------------------------------------- |
304
+ | `kind` | `"mount_faq"` | Yes | Action type |
305
+ | `slot` | string | Yes | Target slot (e.g., `"drawer_right"`, `"overlay_center"`) |
306
+ | `config.title` | string | No | Widget title |
307
+ | `config.expandBehavior` | `"single"` \| `"multiple"` | No | Whether one or many items can be open at once (default: `"single"`) |
308
+ | `config.searchable` | boolean | No | Show a search/filter input (default: `false`) |
309
+ | `config.theme` | `"light"` \| `"dark"` \| `"auto"` | No | Color theme (default: `"auto"`) |
310
+ | `config.items` | array | Yes | FAQ items (see below) |
311
+ | `config.feedback` | boolean \| FeedbackConfig | No | Enable per-item feedback widget |
312
+ | `config.ordering` | OrderingStrategy | No | Item ordering strategy (default: `"static"`) |
313
+ | `config.injections` | InjectionRule[] | No | Dynamic item injection rules |
308
314
 
309
315
  ### FAQ Item Schema
310
316
 
311
317
  Each item in the `items` array:
312
318
 
313
- | Property | Type | Required | Description |
314
- | ---------- | ---------------- | -------- | ----------------------------------- |
315
- | `question` | string | Yes | The question text |
316
- | `answer` | string | Yes | The answer text (supports markdown) |
317
- | `showWhen` | DecisionStrategy | No | Conditional visibility strategy |
319
+ | Property | Type | Required | Description |
320
+ | ----------------------- | ------------------------ | -------- | ----------------------------------------------- |
321
+ | `kind` | `"faq:question"` | Yes | Compositional action type |
322
+ | `config.id` | string | Yes | Unique identifier for this question |
323
+ | `config.question` | string | Yes | The question text |
324
+ | `config.answer` | FAQAnswer | Yes | Answer content (string, rich HTML, or markdown) |
325
+ | `config.category` | string | No | Category for grouping items |
326
+ | `config.priority` | number | No | Priority weight for ordering |
327
+ | `config.answerStrategy` | AnswerStrategy | No | AI-generated answer configuration |
328
+ | `showWhen` | DecisionStrategy \| null | No | Conditional visibility strategy |
318
329
 
319
330
  ```json
320
331
  {
@@ -322,14 +333,34 @@ Each item in the `items` array:
322
333
  "slot": "drawer_right",
323
334
  "config": {
324
335
  "title": "Frequently Asked Questions",
336
+ "expandBehavior": "single",
337
+ "searchable": true,
338
+ "theme": "auto",
339
+ "feedback": {
340
+ "style": "thumbs",
341
+ "prompt": "Was this helpful?"
342
+ },
343
+ "ordering": "priority",
325
344
  "items": [
326
345
  {
327
- "question": "How do I get started?",
328
- "answer": "Sign up for a free account and follow our quickstart guide."
346
+ "kind": "faq:question",
347
+ "config": {
348
+ "id": "getting-started",
349
+ "question": "How do I get started?",
350
+ "answer": "Sign up for a free account and follow our quickstart guide.",
351
+ "category": "General",
352
+ "priority": 10
353
+ }
329
354
  },
330
355
  {
331
- "question": "What payment methods do you accept?",
332
- "answer": "We accept all major credit cards and PayPal.",
356
+ "kind": "faq:question",
357
+ "config": {
358
+ "id": "payment-methods",
359
+ "question": "What payment methods do you accept?",
360
+ "answer": "We accept all major credit cards and PayPal.",
361
+ "category": "Billing",
362
+ "priority": 5
363
+ },
333
364
  "showWhen": {
334
365
  "type": "rules",
335
366
  "rules": [
@@ -346,16 +377,270 @@ Each item in the `items` array:
346
377
  }
347
378
  ```
348
379
 
380
+ ### scroll_to_faq
381
+
382
+ Scrolls the viewport to a specific FAQ item and optionally expands it.
383
+
384
+ | Property | Type | Required | Default | Description |
385
+ | -------------- | ----------------- | -------- | ---------- | ------------------------------------------ |
386
+ | `kind` | `"scroll_to_faq"` | Yes | | Action type |
387
+ | `itemId` | string | No\* | | Target item ID |
388
+ | `itemQuestion` | string | No\* | | Target item question text (fuzzy match) |
389
+ | `expand` | boolean | No | `true` | Whether to expand the item after scrolling |
390
+ | `behavior` | string | No | `"smooth"` | `"smooth"`, `"instant"`, `"auto"` |
391
+
392
+ \* Either `itemId` or `itemQuestion` is required.
393
+
394
+ ```json
395
+ {
396
+ "kind": "scroll_to_faq",
397
+ "itemId": "payment-methods",
398
+ "expand": true,
399
+ "behavior": "smooth"
400
+ }
401
+ ```
402
+
403
+ ### toggle_faq_item
404
+
405
+ Opens, closes, or toggles a FAQ item's expanded state.
406
+
407
+ | Property | Type | Required | Default | Description |
408
+ | -------------- | ------------------- | -------- | ---------- | --------------------------------------- |
409
+ | `kind` | `"toggle_faq_item"` | Yes | | Action type |
410
+ | `itemId` | string | No\* | | Target item ID |
411
+ | `itemQuestion` | string | No\* | | Target item question text (fuzzy match) |
412
+ | `state` | string | No | `"toggle"` | `"open"`, `"closed"`, `"toggle"` |
413
+
414
+ \* Either `itemId` or `itemQuestion` is required.
415
+
416
+ ```json
417
+ {
418
+ "kind": "toggle_faq_item",
419
+ "itemId": "getting-started",
420
+ "state": "open"
421
+ }
422
+ ```
423
+
424
+ ### update_faq
425
+
426
+ Dynamically adds, removes, reorders, or replaces FAQ items at runtime.
427
+
428
+ | Property | Type | Required | Description |
429
+ | ----------- | ------------------- | -------- | ------------------------------------------------------- |
430
+ | `kind` | `"update_faq"` | Yes | Action type |
431
+ | `operation` | string | Yes | `"add"`, `"remove"`, `"reorder"`, `"replace"` |
432
+ | `items` | FAQQuestionAction[] | No | Items to add or replace with (required for add/replace) |
433
+ | `itemId` | string | No | Item to remove (required for remove) |
434
+ | `order` | string[] | No | Ordered list of item IDs (required for reorder) |
435
+ | `position` | string | No | `"prepend"`, `"append"`, `"before"`, `"after"` |
436
+ | `anchorId` | string | No | Reference item for before/after positioning |
437
+
438
+ **Add items:**
439
+
440
+ ```json
441
+ {
442
+ "kind": "update_faq",
443
+ "operation": "add",
444
+ "position": "append",
445
+ "items": [
446
+ {
447
+ "kind": "faq:question",
448
+ "config": {
449
+ "id": "new-feature",
450
+ "question": "What is the new feature?",
451
+ "answer": "Our latest release includes AI-powered recommendations."
452
+ }
453
+ }
454
+ ]
455
+ }
456
+ ```
457
+
458
+ **Remove an item:**
459
+
460
+ ```json
461
+ {
462
+ "kind": "update_faq",
463
+ "operation": "remove",
464
+ "itemId": "outdated-question"
465
+ }
466
+ ```
467
+
468
+ **Reorder items:**
469
+
470
+ ```json
471
+ {
472
+ "kind": "update_faq",
473
+ "operation": "reorder",
474
+ "order": ["getting-started", "new-feature", "payment-methods"]
475
+ }
476
+ ```
477
+
478
+ **Replace all items:**
479
+
480
+ ```json
481
+ {
482
+ "kind": "update_faq",
483
+ "operation": "replace",
484
+ "items": [
485
+ {
486
+ "kind": "faq:question",
487
+ "config": {
488
+ "id": "only-question",
489
+ "question": "Is this the only question?",
490
+ "answer": "Yes, after replacing all items."
491
+ }
492
+ }
493
+ ]
494
+ }
495
+ ```
496
+
349
497
  ## Compositional Pattern
350
498
 
351
- The FAQ widget supports **per-item conditional visibility** using `showWhen` strategies. This allows different FAQ items to appear based on:
499
+ The FAQ widget uses a **compositional action pattern** where `faq:question` actions serve as configuration data rendered by the widget, rather than being executed by the runtime. This allows:
352
500
 
353
- - Current page/route
354
- - User segment or state
355
- - Viewport conditions
356
- - Any other DecisionStrategy condition
501
+ - **Per-item conditional visibility** via `showWhen` strategies -- items can appear or hide based on page URL, user segment, viewport, or any DecisionStrategy condition
502
+ - **Category grouping** -- items with a `category` field are grouped under collapsible section headers
503
+ - **Dynamic injection** -- `injections` rules can add items when trigger conditions are met, supporting contextual FAQ content
504
+ - **Ordering control** -- the `ordering` strategy determines how items are sorted within categories
357
505
 
358
- Items without `showWhen` are always visible.
506
+ Items without `showWhen` are always visible. Items without `category` appear in an ungrouped section.
507
+
508
+ ## Rich Answer Content
509
+
510
+ FAQ answers support three content formats via the `FAQAnswer` union type:
511
+
512
+ - **Plain string** -- simple text, supports basic markdown
513
+ - **Rich HTML** (`{ "type": "rich", "html": "<p>...</p>" }`) -- pre-rendered HTML content
514
+ - **Enhanced markdown** (`{ "type": "markdown", "content": "...", "assets": [...] }`) -- markdown with embedded media assets (images, videos)
515
+
516
+ ```json
517
+ {
518
+ "config": {
519
+ "id": "rich-example",
520
+ "question": "How does the visual editor work?",
521
+ "answer": {
522
+ "type": "markdown",
523
+ "content": "The visual editor lets you create experiments with a point-and-click interface.\n\n![Editor screenshot](asset:editor-screenshot)",
524
+ "assets": [
525
+ {
526
+ "id": "editor-screenshot",
527
+ "type": "image",
528
+ "src": "https://cdn.example.com/editor.png",
529
+ "alt": "Visual editor interface",
530
+ "width": 800,
531
+ "height": 450
532
+ }
533
+ ]
534
+ }
535
+ }
536
+ }
537
+ ```
538
+
539
+ ## Feedback
540
+
541
+ Per-item feedback allows users to rate answer helpfulness. Enable with a boolean or detailed config:
542
+
543
+ - `"feedback": true` -- enables thumbs up/down with default prompt
544
+ - `"feedback": { "style": "thumbs", "prompt": "Was this helpful?" }` -- thumbs with custom prompt
545
+ - `"feedback": { "style": "rating" }` -- numeric rating scale
546
+
547
+ Feedback events are published via `context.publishEvent` for analytics integration.
548
+
549
+ ## Personalization
550
+
551
+ ### Ordering Strategies
552
+
553
+ The `ordering` field controls how FAQ items are sorted:
554
+
555
+ - **`"static"`** (default) -- items appear in the order defined in the config
556
+ - **`"priority"`** -- items are sorted by their `priority` field (higher values first)
557
+ - **Segment-based** -- items are ordered differently per user segment:
558
+
559
+ ```json
560
+ {
561
+ "ordering": {
562
+ "type": "segment",
563
+ "segmentWeights": {
564
+ "new_user": ["getting-started", "pricing", "support"],
565
+ "power_user": ["api-docs", "advanced-config", "integrations"]
566
+ }
567
+ }
568
+ }
569
+ ```
570
+
571
+ ### Contextual Hints (Companion Tooltips)
572
+
573
+ FAQ questions can be surfaced proactively on the page using companion overlay tooltips. When a tooltip CTA has an `actionId` matching `faq:open:<questionId>`, clicking it expands the corresponding question in the FAQ widget and scrolls it into view.
574
+
575
+ This is a convention-based integration — no special FAQ action is needed. Use a standard `overlays:tooltip` action with `ctaButtons`:
576
+
577
+ ```json
578
+ {
579
+ "kind": "overlays:tooltip",
580
+ "anchorId": "#complex-feature",
581
+ "content": {
582
+ "title": "Need help?",
583
+ "body": "Check our FAQ for details.",
584
+ "ctaButtons": [
585
+ { "label": "View Answer", "actionId": "faq:open:getting-started", "primary": true },
586
+ { "label": "Dismiss", "actionId": "dismiss" }
587
+ ]
588
+ },
589
+ "trigger": "immediate",
590
+ "placement": "bottom"
591
+ }
592
+ ```
593
+
594
+ **How it works:**
595
+
596
+ 1. The tooltip CTA click publishes an `action.tooltip_cta_clicked` event via the EventBus
597
+ 2. The FAQ widget subscribes to these events and filters for `faq:open:*` actionIds
598
+ 3. The matching question expands and scrolls into view
599
+ 4. The widget also publishes `canvas.requestOpen` to open the canvas panel if needed
600
+
601
+ **Late-mount support:** If the FAQ widget mounts after the CTA click (e.g., canvas was closed), it checks the EventBus history for recent `faq:open:*` events (within 10 seconds) and auto-expands the target question on mount.
602
+
603
+ **`dismiss` actionId:** Destroys the tooltip without publishing an event. Use this for a "close" or "not now" button.
604
+
605
+ ### Dynamic Injection
606
+
607
+ Injection rules add contextual FAQ items when conditions are met:
608
+
609
+ ```json
610
+ {
611
+ "injections": [
612
+ {
613
+ "trigger": {
614
+ "type": "rules",
615
+ "rules": [
616
+ {
617
+ "conditions": [{ "type": "page_url", "pattern": "/checkout*" }],
618
+ "value": true
619
+ }
620
+ ],
621
+ "default": false
622
+ },
623
+ "items": [
624
+ {
625
+ "kind": "faq:question",
626
+ "config": {
627
+ "id": "checkout-help",
628
+ "question": "Having trouble with checkout?",
629
+ "answer": "Contact support at help@example.com for immediate assistance."
630
+ }
631
+ }
632
+ ],
633
+ "position": "prepend",
634
+ "once": true
635
+ }
636
+ ]
637
+ }
638
+ ```
639
+
640
+ - `trigger` -- a DecisionStrategy that evaluates to `true` when injection should occur
641
+ - `items` -- FAQ items to inject
642
+ - `position` -- `"prepend"` or `"append"` relative to existing items
643
+ - `once` -- if `true`, items are injected only on the first trigger match
359
644
 
360
645
 
361
646
  ---
@@ -449,71 +734,81 @@ Mounts gamification UI elements.
449
734
 
450
735
  # @syntrologie/adapt-nav
451
736
 
452
- Navigation link list widget with conditional item visibility.
737
+ Navigation tips accordion widget with conditional item visibility and toast notifications.
453
738
 
454
- ## Actions
739
+ ## Widget: `adaptive-nav:tips`
455
740
 
456
- ### mount_nav
741
+ Accordion of contextual navigation tips. Each tip has a collapsible header and expanded body with optional CTA link.
457
742
 
458
- Mounts a navigation link list widget to a surface slot.
743
+ ### Tile Config
459
744
 
460
- | Property | Type | Required | Description |
461
- | -------------- | ------------- | -------- | ---------------------------------------------------------- |
462
- | `kind` | `"mount_nav"` | Yes | Action type |
463
- | `slot` | string | Yes | Target slot (e.g., `"drawer_left"`, `"inline:{anchorId}"`) |
464
- | `config.title` | string | No | Widget title |
465
- | `config.items` | array | Yes | Navigation items (see below) |
745
+ | Property | Type | Required | Default | Description |
746
+ | ------------------------ | -------------------------- | -------- | ---------- | ------------------------------- |
747
+ | `widget` | `"adaptive-nav:tips"` | Yes | | Widget identifier |
748
+ | `props.expandBehavior` | `"single"` \| `"multiple"` | No | `"single"` | Accordion expand mode |
749
+ | `props.theme` | `"light"` \| `"dark"` \| `"auto"` | No | `"auto"` | Color theme |
750
+ | `props.actions` | `NavTipAction[]` | Yes | | Navigation tip items |
466
751
 
467
- ### Nav Item Schema
752
+ ### Nav Tip Action (`nav:tip`)
468
753
 
469
- Each item in the `items` array:
754
+ Compositional action — rendered as an accordion item by the parent widget.
470
755
 
471
- | Property | Type | Required | Description |
472
- | ---------- | ---------------- | -------- | ------------------------------- |
473
- | `label` | string | Yes | Link text |
474
- | `href` | string | Yes | Link destination |
475
- | `icon` | string | No | Icon identifier |
476
- | `showWhen` | DecisionStrategy | No | Conditional visibility strategy |
756
+ | Property | Type | Required | Description |
757
+ | ---------------------- | ---------------------------- | -------- | ------------------------------------ |
758
+ | `kind` | `"nav:tip"` | Yes | Action type |
759
+ | `config.id` | string | Yes | Unique tip identifier |
760
+ | `config.title` | string | Yes | Accordion header text |
761
+ | `config.description` | string | Yes | Expanded body text |
762
+ | `config.href` | string | No | Optional CTA link URL |
763
+ | `config.icon` | string | No | Icon (emoji or icon key) |
764
+ | `config.external` | boolean | No | Open link in new tab |
765
+ | `config.category` | string | No | Category for grouping |
766
+ | `showWhen` | `DecisionStrategy<boolean>` | No | Conditional visibility |
767
+ | `notify` | `{ title?, body?, icon? }` | No | Toast config for showWhen transition |
768
+ | `rationale` | `{ why, confidence? }` | No | AI reasoning for recommendation |
769
+
770
+ ### Events Published
771
+
772
+ | Event | When | Props |
773
+ | ------------------ | ------------------------------------- | ------------------------------------ |
774
+ | `nav:toggled` | User expands/collapses a tip | `{ instanceId, tipId, expanded }` |
775
+ | `nav:tip_clicked` | User clicks a tip's CTA link | `{ instanceId, href, external }` |
776
+ | `nav:tip_revealed` | `showWhen` transitions false → true | `{ tipId, title, body, icon }` |
777
+
778
+ ### Notify Watchers
779
+
780
+ Tips with both `showWhen` and `notify` are registered as notify watchers. The runtime evaluates these continuously (even when drawer is closed). When `showWhen` transitions false → true, publishes `nav:tip_revealed` which can trigger toast notifications via tile-level `notifications` rules.
477
781
 
478
782
  ```json
479
783
  {
480
- "kind": "mount_nav",
481
- "slot": "drawer_left",
482
- "config": {
483
- "title": "Quick Links",
484
- "items": [
485
- {
486
- "label": "Dashboard",
487
- "href": "/dashboard",
488
- "icon": "home"
489
- },
490
- {
491
- "label": "Admin Settings",
492
- "href": "/admin",
493
- "icon": "settings",
494
- "showWhen": {
495
- "type": "rules",
496
- "rules": [
497
- {
498
- "conditions": [{ "type": "state_equals", "key": "user.role", "value": "admin" }],
499
- "value": true
500
- }
501
- ],
502
- "default": false
503
- }
504
- },
784
+ "id": "nav",
785
+ "widget": "adaptive-nav:tips",
786
+ "notifications": [
787
+ {
788
+ "on": "nav:tip_revealed",
789
+ "title": "{{props.title}}",
790
+ "body": "{{props.body}}",
791
+ "icon": "🧭"
792
+ }
793
+ ],
794
+ "props": {
795
+ "expandBehavior": "single",
796
+ "theme": "dark",
797
+ "actions": [
505
798
  {
506
- "label": "Upgrade",
507
- "href": "/pricing",
508
- "icon": "sparkles",
799
+ "kind": "nav:tip",
800
+ "config": {
801
+ "id": "tip-analytics",
802
+ "title": "Advanced Analytics",
803
+ "description": "Unlock deeper insights with our analytics suite.",
804
+ "href": "/analytics",
805
+ "icon": "📊",
806
+ "category": "Power User"
807
+ },
808
+ "notify": { "title": "New Tip", "body": "Advanced Analytics", "icon": "📊" },
509
809
  "showWhen": {
510
810
  "type": "rules",
511
- "rules": [
512
- {
513
- "conditions": [{ "type": "state_equals", "key": "user.plan", "value": "free" }],
514
- "value": true
515
- }
516
- ],
811
+ "rules": [{ "conditions": [{ "type": "event_count", "key": "link-clicks", "operator": "gte", "count": 3 }], "value": true }],
517
812
  "default": false
518
813
  }
519
814
  }
@@ -522,57 +817,28 @@ Each item in the `items` array:
522
817
  }
523
818
  ```
524
819
 
525
- ## Compositional Pattern
526
-
527
- The nav widget supports **per-item conditional visibility** using `showWhen` strategies. This allows different navigation items to appear based on:
528
-
529
- - User role or permissions
530
- - Subscription tier
531
- - Feature flags
532
- - Any other DecisionStrategy condition
533
-
534
- Items without `showWhen` are always visible.
535
-
536
820
  ## Navigation Actions
537
821
 
538
- ### scroll_to
822
+ ### `navigation:scrollTo`
539
823
 
540
824
  Scrolls the viewport to bring an element into view.
541
825
 
542
826
  | Property | Type | Required | Default | Description |
543
827
  | ---------- | ------------- | -------- | ----------- | ------------------------------------------- |
544
- | `kind` | `"scroll_to"` | Yes | | Action type |
828
+ | `kind` | `"navigation:scrollTo"` | Yes | | Action type |
545
829
  | `anchorId` | string | Yes | | Element selector |
546
830
  | `behavior` | string | No | `"smooth"` | `"smooth"`, `"instant"`, `"auto"` |
547
831
  | `block` | string | No | `"center"` | `"start"`, `"center"`, `"end"`, `"nearest"` |
548
- | `inline` | string | No | `"nearest"` | `"start"`, `"center"`, `"end"`, `"nearest"` |
549
-
550
- ```json
551
- {
552
- "kind": "scroll_to",
553
- "anchorId": "#pricing-section",
554
- "behavior": "smooth",
555
- "block": "start"
556
- }
557
- ```
558
832
 
559
- ### navigate
833
+ ### `navigation:navigate`
560
834
 
561
835
  Navigates to a URL.
562
836
 
563
- | Property | Type | Required | Default | Description |
564
- | -------- | ------------ | -------- | --------- | ----------------------- |
565
- | `kind` | `"navigate"` | Yes | | Action type |
566
- | `url` | string | Yes | | Destination URL |
567
- | `target` | string | No | `"_self"` | `"_self"` or `"_blank"` |
568
-
569
- ```json
570
- {
571
- "kind": "navigate",
572
- "url": "/signup?ref=banner",
573
- "target": "_self"
574
- }
575
- ```
837
+ | Property | Type | Required | Default | Description |
838
+ | -------- | ----------------------- | -------- | --------- | ----------------------- |
839
+ | `kind` | `"navigation:navigate"` | Yes | | Action type |
840
+ | `url` | string | Yes | | Destination URL |
841
+ | `target` | string | No | `"_self"` | `"_self"` or `"_blank"` |
576
842
 
577
843
  **Note:** `javascript:` URLs are blocked for security.
578
844
 
@@ -619,13 +885,16 @@ Shows a tooltip near an element with optional title, body, and CTA.
619
885
  | `anchorId` | string | Yes | | Element selector |
620
886
  | `content.title` | string | No | | Tooltip heading |
621
887
  | `content.body` | string | Yes | | Tooltip text |
622
- | `content.cta.label` | string | No | | CTA button text |
623
- | `content.cta.action` | Action | No | | Action to execute on CTA click |
624
- | `trigger` | string | No | `"immediate"` | `"immediate"`, `"hover"`, `"click"` |
625
- | `placement` | string | No | `"top"` | See placement options below |
888
+ | `content.cta.label` | string | No | | CTA button text (single-CTA shorthand) |
889
+ | `content.cta.action` | Action | No | | Action to execute on CTA click |
890
+ | `content.ctaButtons` | array | No | | Multiple CTA buttons (see below) |
891
+ | `trigger` | string | No | `"immediate"` | `"immediate"`, `"hover"`, `"click"` |
892
+ | `placement` | string | No | `"top"` | See placement options below |
626
893
 
627
894
  **Placement options:** `top`, `top-start`, `top-end`, `bottom`, `bottom-start`, `bottom-end`, `left`, `left-start`, `left-end`, `right`, `right-start`, `right-end`
628
895
 
896
+ **Single CTA (shorthand):**
897
+
629
898
  ```json
630
899
  {
631
900
  "kind": "tooltip",
@@ -643,6 +912,37 @@ Shows a tooltip near an element with optional title, body, and CTA.
643
912
  }
644
913
  ```
645
914
 
915
+ **Multiple CTAs (`ctaButtons`):**
916
+
917
+ Use `ctaButtons` for tooltips with multiple actions. Each button has:
918
+
919
+ - `label`: Button text
920
+ - `actionId`: Identifier published with the `action.tooltip_cta_clicked` event
921
+ - `primary`: Whether this is a primary button (default: false)
922
+
923
+ ```json
924
+ {
925
+ "kind": "overlays:tooltip",
926
+ "anchorId": "[data-nav=\"features\"]",
927
+ "content": {
928
+ "title": "Learn About Features",
929
+ "body": "We have answers about our feature set.",
930
+ "ctaButtons": [
931
+ { "label": "View Answer", "actionId": "faq:open:q-features", "primary": true },
932
+ { "label": "Dismiss", "actionId": "dismiss" }
933
+ ]
934
+ },
935
+ "trigger": "immediate",
936
+ "placement": "bottom"
937
+ }
938
+ ```
939
+
940
+ **Special actionId values:**
941
+
942
+ - `"dismiss"` — Destroys the tooltip immediately without publishing an event
943
+ - `"faq:open:<questionId>"` — Convention for companion FAQ tooltips (see adaptive-faq CAPABILITIES for details). Publishes `action.tooltip_cta_clicked` which the FAQ widget listens for.
944
+ - Any other value — Publishes `action.tooltip_cta_clicked` with the `actionId` in event props for custom handling
945
+
646
946
  ### badge
647
947
 
648
948
  Adds a small badge indicator near an element.
@@ -1,5 +1,4 @@
1
1
  import { ReactNode } from 'react';
2
- import { MountableComponent } from './api';
3
2
  import { CanvasTheme } from './components/ShadowCanvasOverlay';
4
3
  import { SmartCanvasController } from './controller';
5
4
  import type { ExperimentClient } from './experiments/types';
@@ -29,7 +28,6 @@ export interface SmartCanvasAppProps {
29
28
  footerSlot?: ReactNode;
30
29
  launcherLabel?: string;
31
30
  canvasHost?: HTMLElement | null;
32
- customRenderers?: Record<string, MountableComponent>;
33
31
  theme?: Partial<CanvasTheme>;
34
32
  }
35
- export declare function SmartCanvasApp({ controller, fetcher, configUri, configUriFeatureKey, configFeatureKey, fetchCredentials, pollIntervalMs, experiments, telemetry, runtime, overlayFetcher, overlayConfigUri, overlayConfigFeatureKey, overlayFetchCredentials, footerSlot, launcherLabel, canvasHost, customRenderers, theme, }: SmartCanvasAppProps): import("react/jsx-runtime").JSX.Element;
33
+ export declare function SmartCanvasApp({ controller, fetcher, configUri, configUriFeatureKey, configFeatureKey, fetchCredentials, pollIntervalMs, experiments, telemetry, runtime, overlayFetcher, overlayConfigUri, overlayConfigFeatureKey, overlayFetchCredentials, footerSlot, launcherLabel, canvasHost, theme, }: SmartCanvasAppProps): import("react/jsx-runtime").JSX.Element;