# SmartCanvas SDK Capabilities

This document describes all available operations and capabilities of the SmartCanvas SDK for DOM manipulation, interventions, and UI rendering.

> **Auto-generated**: This file is assembled from individual adaptive package capabilities during build.

## Table of Contents
- [Overview](#overview)
- [Quick Start Example](#quick-start-example)
- [Adaptive Packages](#adaptive-packages)
- [Surfaces](#surfaces)
- [Anchor Resolution](#anchor-resolution)
- [Decision Strategies](#decision-strategies)
- [Best Practices](#best-practices)

---

## Overview

The SmartCanvas SDK provides three main systems for modifying web pages:

1. **ActionEngine** - Unified execution layer for interventions (highlight, tooltip, badge, DOM modifications)
2. **Surfaces** - Managed surface system for rendering UI into named slots
3. **Runtime** - Context, events, state, and decision management

All modifications are reversible and publish events to the EventBus for tracking.

---

## Quick Start Example

### Simple Header Text Change

Change a page header when the element is visible:

```json
{
  "id": "welcome-header-change",
  "activation": {
    "routes": { "include": ["/", "/home"] },
    "strategy": {
      "type": "rules",
      "rules": [
        {
          "conditions": [
            { "type": "anchor_visible", "anchorId": "h1.hero-title", "state": "visible" }
          ],
          "value": true
        }
      ],
      "default": false
    }
  },
  "actions": [
    {
      "kind": "set_text",
      "anchorId": "h1.hero-title",
      "text": "Welcome to Our New Experience"
    }
  ]
}
```

---

## Adaptive Packages

The SDK includes the following adaptive packages, each providing specific capabilities:

- [@syntrologie/adapt-chatbot](#syntrologieadapt-chatbot)
- [@syntrologie/adapt-content](#syntrologieadapt-content)
- [@syntrologie/adapt-faq](#syntrologieadapt-faq)
- [@syntrologie/adapt-gamification](#syntrologieadapt-gamification)
- [@syntrologie/adapt-nav](#syntrologieadapt-nav)
- [@syntrologie/adapt-overlays](#syntrologieadapt-overlays)

---

# @syntrologie/adapt-chatbot

AI chat assistant widget with action execution capabilities.

## Widgets

### adaptive-chatbot:assistant

Mounts an AI-powered chat assistant that connects to a backend LLM endpoint. The assistant can parse action instructions from the LLM response and execute them on the page via the runtime's ActionEngine.

**Tile config example:**

```json
{
  "id": "chatbot-assistant",
  "title": "Ask Assistant",
  "content": {
    "type": "custom",
    "component": "adaptive-chatbot:assistant",
    "props": {
      "backendUrl": "/api/chat/message",
      "mlflowRunId": "abc123",
      "greeting": "Hi! How can I help?"
    }
  },
  "size": "full",
  "defaultExpanded": true
}
```

### Config Props

| Property      | Type   | Required | Default               | Description                             |
| ------------- | ------ | -------- | --------------------- | --------------------------------------- |
| `backendUrl`  | string | Yes      | —                     | URL for the chat API endpoint           |
| `mlflowRunId` | string | No       | —                     | MLflow run ID for experiment tracking   |
| `greeting`    | string | No       | "Hi! How can I help?" | Initial greeting message                |
| `maxHistory`  | number | No       | 20                    | Max messages sent as history to backend |

## Action Execution

The chatbot does **not** define its own action kinds. Instead, it parses JSON action blocks from the LLM response and executes them via `runtime.actions.applyBatch()`. This means the chatbot can trigger any action kind registered in the runtime (highlights, tooltips, text changes, navigation, tours, etc.).

### LLM Response Format

The backend returns a text reply that may contain embedded JSON action blocks in fenced code blocks:

```
Here's what I found. Let me highlight the pricing section for you.

\`\`\`json
{"kind": "overlays:highlight", "anchorId": "pricing-section"}
\`\`\`

Is there anything else you'd like to know?
```

- JSON blocks with a `kind` field are extracted as actions and removed from display text
- JSON blocks without `kind` are left in the display text as-is
- Multiple action blocks can appear in a single response
- Actions with unknown kinds are silently ignored by the ActionEngine

## Authentication

The widget reads auth credentials from the browser:

- **JWT**: `stytch_session_jwt` cookie
- **Workspace ID**: `syntrologie_workspace_id` from localStorage
- Headers: `Authorization: Bearer <jwt>` + `X-Workspace-Id: <id>`

## Events

| Event                     | Props                        | Description                       |
| ------------------------- | ---------------------------- | --------------------------------- |
| `chatbot.actions_applied` | `count`, `kinds[]`, `tileId` | Emitted when actions are executed |


---

# @syntrologie/adapt-content

DOM content modification capabilities for text, attributes, styles, HTML, and classes.

## Actions

### set_text

Replaces the text content of an element.

| Property   | Type         | Required | Description      |
| ---------- | ------------ | -------- | ---------------- |
| `kind`     | `"set_text"` | Yes      | Action type      |
| `anchorId` | string       | Yes      | Element selector |
| `text`     | string       | Yes      | New text content |

```json
{
  "kind": "set_text",
  "anchorId": "h1.hero-title",
  "text": "Start Your Free Trial Today"
}
```

### set_attr

Sets an HTML attribute on an element.

| Property   | Type         | Required | Description      |
| ---------- | ------------ | -------- | ---------------- |
| `kind`     | `"set_attr"` | Yes      | Action type      |
| `anchorId` | string       | Yes      | Element selector |
| `attr`     | string       | Yes      | Attribute name   |
| `value`    | string       | Yes      | Attribute value  |

**Blocked attributes:** Event handlers (`onclick`, `onerror`, etc.) are not allowed.

```json
{
  "kind": "set_attr",
  "anchorId": "#signup-form",
  "attr": "data-experiment",
  "value": "signup-v2"
}
```

### set_style

Sets inline CSS styles on an element.

| Property   | Type          | Required | Description              |
| ---------- | ------------- | -------- | ------------------------ |
| `kind`     | `"set_style"` | Yes      | Action type              |
| `anchorId` | string        | Yes      | Element selector         |
| `styles`   | object        | Yes      | CSS property/value pairs |

```json
{
  "kind": "set_style",
  "anchorId": ".hero-section",
  "styles": {
    "background-color": "#1e40af",
    "padding": "2rem"
  }
}
```

### insert_html

Inserts HTML content relative to an element.

| Property   | Type            | Required | Description                                                 |
| ---------- | --------------- | -------- | ----------------------------------------------------------- |
| `kind`     | `"insert_html"` | Yes      | Action type                                                 |
| `anchorId` | string          | Yes      | Element selector                                            |
| `html`     | string          | Yes      | HTML content (sanitized)                                    |
| `position` | string          | Yes      | `"before"`, `"after"`, `"prepend"`, `"append"`, `"replace"` |

**Positions:**

- `before` - Insert before the element
- `after` - Insert after the element
- `prepend` - Insert inside, before first child
- `append` - Insert inside, after last child
- `replace` - Replace the entire element

```json
{
  "kind": "insert_html",
  "anchorId": ".cta-button",
  "html": "<span class=\"badge\">NEW</span>",
  "position": "append"
}
```

### add_class

Adds a CSS class to an element.

| Property    | Type          | Required | Description       |
| ----------- | ------------- | -------- | ----------------- |
| `kind`      | `"add_class"` | Yes      | Action type       |
| `anchorId`  | string        | Yes      | Element selector  |
| `className` | string        | Yes      | Class name to add |

```json
{
  "kind": "add_class",
  "anchorId": ".pricing-card",
  "className": "highlighted"
}
```

### remove_class

Removes a CSS class from an element.

| Property    | Type             | Required | Description          |
| ----------- | ---------------- | -------- | -------------------- |
| `kind`      | `"remove_class"` | Yes      | Action type          |
| `anchorId`  | string           | Yes      | Element selector     |
| `className` | string           | Yes      | Class name to remove |

```json
{
  "kind": "remove_class",
  "anchorId": ".pricing-card",
  "className": "hidden"
}
```


---

# @syntrologie/adapt-faq

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.

## Actions

### mount_faq

Mounts an FAQ accordion widget to a surface slot.

| Property                | Type                              | Required | Description                                                         |
| ----------------------- | --------------------------------- | -------- | ------------------------------------------------------------------- |
| `kind`                  | `"mount_faq"`                     | Yes      | Action type                                                         |
| `slot`                  | string                            | Yes      | Target slot (e.g., `"drawer_right"`, `"overlay_center"`)            |
| `config.title`          | string                            | No       | Widget title                                                        |
| `config.expandBehavior` | `"single"` \| `"multiple"`        | No       | Whether one or many items can be open at once (default: `"single"`) |
| `config.searchable`     | boolean                           | No       | Show a search/filter input (default: `false`)                       |
| `config.theme`          | `"light"` \| `"dark"` \| `"auto"` | No       | Color theme (default: `"auto"`)                                     |
| `config.items`          | array                             | Yes      | FAQ items (see below)                                               |
| `config.feedback`       | boolean \| FeedbackConfig         | No       | Enable per-item feedback widget                                     |
| `config.ordering`       | OrderingStrategy                  | No       | Item ordering strategy (default: `"static"`)                        |
| `config.injections`     | InjectionRule[]                   | No       | Dynamic item injection rules                                        |

### FAQ Item Schema

Each item in the `items` array:

| Property                | Type                     | Required | Description                                     |
| ----------------------- | ------------------------ | -------- | ----------------------------------------------- |
| `kind`                  | `"faq:question"`         | Yes      | Compositional action type                       |
| `config.id`             | string                   | Yes      | Unique identifier for this question             |
| `config.question`       | string                   | Yes      | The question text                               |
| `config.answer`         | FAQAnswer                | Yes      | Answer content (string, rich HTML, or markdown) |
| `config.category`       | string                   | No       | Category for grouping items                     |
| `config.priority`       | number                   | No       | Priority weight for ordering                    |
| `config.answerStrategy` | AnswerStrategy           | No       | AI-generated answer configuration               |
| `showWhen`              | DecisionStrategy \| null | No       | Conditional visibility strategy                 |

```json
{
  "kind": "mount_faq",
  "slot": "drawer_right",
  "config": {
    "title": "Frequently Asked Questions",
    "expandBehavior": "single",
    "searchable": true,
    "theme": "auto",
    "feedback": {
      "style": "thumbs",
      "prompt": "Was this helpful?"
    },
    "ordering": "priority",
    "items": [
      {
        "kind": "faq:question",
        "config": {
          "id": "getting-started",
          "question": "How do I get started?",
          "answer": "Sign up for a free account and follow our quickstart guide.",
          "category": "General",
          "priority": 10
        }
      },
      {
        "kind": "faq:question",
        "config": {
          "id": "payment-methods",
          "question": "What payment methods do you accept?",
          "answer": "We accept all major credit cards and PayPal.",
          "category": "Billing",
          "priority": 5
        },
        "showWhen": {
          "type": "rules",
          "rules": [
            {
              "conditions": [{ "type": "page_url", "pattern": "/pricing*" }],
              "value": true
            }
          ],
          "default": false
        }
      }
    ]
  }
}
```

### scroll_to_faq

Scrolls the viewport to a specific FAQ item and optionally expands it.

| Property       | Type              | Required | Default    | Description                                |
| -------------- | ----------------- | -------- | ---------- | ------------------------------------------ |
| `kind`         | `"scroll_to_faq"` | Yes      |            | Action type                                |
| `itemId`       | string            | No\*     |            | Target item ID                             |
| `itemQuestion` | string            | No\*     |            | Target item question text (fuzzy match)    |
| `expand`       | boolean           | No       | `true`     | Whether to expand the item after scrolling |
| `behavior`     | string            | No       | `"smooth"` | `"smooth"`, `"instant"`, `"auto"`          |

\* Either `itemId` or `itemQuestion` is required.

```json
{
  "kind": "scroll_to_faq",
  "itemId": "payment-methods",
  "expand": true,
  "behavior": "smooth"
}
```

### toggle_faq_item

Opens, closes, or toggles a FAQ item's expanded state.

| Property       | Type                | Required | Default    | Description                             |
| -------------- | ------------------- | -------- | ---------- | --------------------------------------- |
| `kind`         | `"toggle_faq_item"` | Yes      |            | Action type                             |
| `itemId`       | string              | No\*     |            | Target item ID                          |
| `itemQuestion` | string              | No\*     |            | Target item question text (fuzzy match) |
| `state`        | string              | No       | `"toggle"` | `"open"`, `"closed"`, `"toggle"`        |

\* Either `itemId` or `itemQuestion` is required.

```json
{
  "kind": "toggle_faq_item",
  "itemId": "getting-started",
  "state": "open"
}
```

### update_faq

Dynamically adds, removes, reorders, or replaces FAQ items at runtime.

| Property    | Type                | Required | Description                                             |
| ----------- | ------------------- | -------- | ------------------------------------------------------- |
| `kind`      | `"update_faq"`      | Yes      | Action type                                             |
| `operation` | string              | Yes      | `"add"`, `"remove"`, `"reorder"`, `"replace"`           |
| `items`     | FAQQuestionAction[] | No       | Items to add or replace with (required for add/replace) |
| `itemId`    | string              | No       | Item to remove (required for remove)                    |
| `order`     | string[]            | No       | Ordered list of item IDs (required for reorder)         |
| `position`  | string              | No       | `"prepend"`, `"append"`, `"before"`, `"after"`          |
| `anchorId`  | string              | No       | Reference item for before/after positioning             |

**Add items:**

```json
{
  "kind": "update_faq",
  "operation": "add",
  "position": "append",
  "items": [
    {
      "kind": "faq:question",
      "config": {
        "id": "new-feature",
        "question": "What is the new feature?",
        "answer": "Our latest release includes AI-powered recommendations."
      }
    }
  ]
}
```

**Remove an item:**

```json
{
  "kind": "update_faq",
  "operation": "remove",
  "itemId": "outdated-question"
}
```

**Reorder items:**

```json
{
  "kind": "update_faq",
  "operation": "reorder",
  "order": ["getting-started", "new-feature", "payment-methods"]
}
```

**Replace all items:**

```json
{
  "kind": "update_faq",
  "operation": "replace",
  "items": [
    {
      "kind": "faq:question",
      "config": {
        "id": "only-question",
        "question": "Is this the only question?",
        "answer": "Yes, after replacing all items."
      }
    }
  ]
}
```

## Compositional Pattern

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:

- **Per-item conditional visibility** via `showWhen` strategies -- items can appear or hide based on page URL, user segment, viewport, or any DecisionStrategy condition
- **Category grouping** -- items with a `category` field are grouped under collapsible section headers
- **Dynamic injection** -- `injections` rules can add items when trigger conditions are met, supporting contextual FAQ content
- **Ordering control** -- the `ordering` strategy determines how items are sorted within categories

Items without `showWhen` are always visible. Items without `category` appear in an ungrouped section.

## Rich Answer Content

FAQ answers support three content formats via the `FAQAnswer` union type:

- **Plain string** -- simple text, supports basic markdown
- **Rich HTML** (`{ "type": "rich", "html": "<p>...</p>" }`) -- pre-rendered HTML content
- **Enhanced markdown** (`{ "type": "markdown", "content": "...", "assets": [...] }`) -- markdown with embedded media assets (images, videos)

```json
{
  "config": {
    "id": "rich-example",
    "question": "How does the visual editor work?",
    "answer": {
      "type": "markdown",
      "content": "The visual editor lets you create experiments with a point-and-click interface.\n\n![Editor screenshot](asset:editor-screenshot)",
      "assets": [
        {
          "id": "editor-screenshot",
          "type": "image",
          "src": "https://cdn.example.com/editor.png",
          "alt": "Visual editor interface",
          "width": 800,
          "height": 450
        }
      ]
    }
  }
}
```

## Feedback

Per-item feedback allows users to rate answer helpfulness. Enable with a boolean or detailed config:

- `"feedback": true` -- enables thumbs up/down with default prompt
- `"feedback": { "style": "thumbs", "prompt": "Was this helpful?" }` -- thumbs with custom prompt
- `"feedback": { "style": "rating" }` -- numeric rating scale

Feedback events are published via `context.publishEvent` for analytics integration.

## Personalization

### Ordering Strategies

The `ordering` field controls how FAQ items are sorted:

- **`"static"`** (default) -- items appear in the order defined in the config
- **`"priority"`** -- items are sorted by their `priority` field (higher values first)
- **Segment-based** -- items are ordered differently per user segment:

```json
{
  "ordering": {
    "type": "segment",
    "segmentWeights": {
      "new_user": ["getting-started", "pricing", "support"],
      "power_user": ["api-docs", "advanced-config", "integrations"]
    }
  }
}
```

### Contextual Hints (Companion Tooltips)

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.

This is a convention-based integration — no special FAQ action is needed. Use a standard `overlays:tooltip` action with `ctaButtons`:

```json
{
  "kind": "overlays:tooltip",
  "anchorId": "#complex-feature",
  "content": {
    "title": "Need help?",
    "body": "Check our FAQ for details.",
    "ctaButtons": [
      { "label": "View Answer", "actionId": "faq:open:getting-started", "primary": true },
      { "label": "Dismiss", "actionId": "dismiss" }
    ]
  },
  "trigger": "immediate",
  "placement": "bottom"
}
```

**How it works:**

1. The tooltip CTA click publishes an `action.tooltip_cta_clicked` event via the EventBus
2. The FAQ widget subscribes to these events and filters for `faq:open:*` actionIds
3. The matching question expands and scrolls into view
4. The widget also publishes `canvas.requestOpen` to open the canvas panel if needed

**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.

**`dismiss` actionId:** Destroys the tooltip without publishing an event. Use this for a "close" or "not now" button.

### Dynamic Injection

Injection rules add contextual FAQ items when conditions are met:

```json
{
  "injections": [
    {
      "trigger": {
        "type": "rules",
        "rules": [
          {
            "conditions": [{ "type": "page_url", "pattern": "/checkout*" }],
            "value": true
          }
        ],
        "default": false
      },
      "items": [
        {
          "kind": "faq:question",
          "config": {
            "id": "checkout-help",
            "question": "Having trouble with checkout?",
            "answer": "Contact support at help@example.com for immediate assistance."
          }
        }
      ],
      "position": "prepend",
      "once": true
    }
  ]
}
```

- `trigger` -- a DecisionStrategy that evaluates to `true` when injection should occur
- `items` -- FAQ items to inject
- `position` -- `"prepend"` or `"append"` relative to existing items
- `once` -- if `true`, items are injected only on the first trigger match


---

# @syntrologie/adapt-gamification

Gamification capabilities including badges, points, and leaderboards.

## Actions

### mount_gamification

Mounts gamification UI elements.

| Property | Type                   | Required | Description                |
| -------- | ---------------------- | -------- | -------------------------- |
| `kind`   | `"mount_gamification"` | Yes      | Action type                |
| `slot`   | string                 | Yes      | Target slot                |
| `config` | object                 | Yes      | Gamification configuration |

### Configuration Schema

| Property                      | Type    | Required | Default | Description            |
| ----------------------------- | ------- | -------- | ------- | ---------------------- |
| `badges`                      | array   | No       | `[]`    | Badge definitions      |
| `points.enabled`              | boolean | No       | `false` | Enable points system   |
| `points.multiplier`           | number  | No       | `1`     | Points multiplier      |
| `leaderboard.enabled`         | boolean | No       | `false` | Show leaderboard       |
| `leaderboard.refreshInterval` | number  | No       | `60000` | Refresh interval in ms |

### Badge Schema

| Property             | Type   | Required | Description                   |
| -------------------- | ------ | -------- | ----------------------------- |
| `id`                 | string | Yes      | Unique badge identifier       |
| `name`               | string | Yes      | Display name                  |
| `icon`               | string | Yes      | Icon identifier               |
| `description`        | string | No       | Badge description             |
| `trigger.event`      | string | Yes      | Event that triggers the badge |
| `trigger.conditions` | array  | No       | Additional conditions         |

```json
{
  "kind": "mount_gamification",
  "slot": "overlay_corner_br",
  "config": {
    "badges": [
      {
        "id": "first-purchase",
        "name": "First Purchase",
        "icon": "shopping-cart",
        "description": "Made your first purchase!",
        "trigger": {
          "event": "purchase_completed"
        }
      },
      {
        "id": "explorer",
        "name": "Explorer",
        "icon": "compass",
        "description": "Visited 10 different pages",
        "trigger": {
          "event": "page_view",
          "conditions": [
            { "type": "session_metric", "key": "unique_pages", "operator": ">=", "threshold": 10 }
          ]
        }
      }
    ],
    "points": {
      "enabled": true,
      "multiplier": 2
    },
    "leaderboard": {
      "enabled": true,
      "refreshInterval": 30000
    }
  }
}
```

## Use Cases

- **Engagement rewards**: Award badges for completing onboarding steps
- **Loyalty programs**: Track points for purchases or interactions
- **Social proof**: Display leaderboards to encourage participation
- **Progress tracking**: Show achievement progress to motivate users


---

# @syntrologie/adapt-nav

Navigation tips accordion widget with conditional item visibility and toast notifications.

## Widget: `adaptive-nav:tips`

Accordion of contextual navigation tips. Each tip has a collapsible header and expanded body with optional CTA link.

### Tile Config

| Property               | Type                              | Required | Default    | Description           |
| ---------------------- | --------------------------------- | -------- | ---------- | --------------------- |
| `widget`               | `"adaptive-nav:tips"`             | Yes      |            | Widget identifier     |
| `props.expandBehavior` | `"single"` \| `"multiple"`        | No       | `"single"` | Accordion expand mode |
| `props.theme`          | `"light"` \| `"dark"` \| `"auto"` | No       | `"auto"`   | Color theme           |
| `props.actions`        | `NavTipAction[]`                  | Yes      |            | Navigation tip items  |

### Nav Tip Action (`nav:tip`)

Compositional action — rendered as an accordion item by the parent widget.

| Property             | Type                        | Required | Description                          |
| -------------------- | --------------------------- | -------- | ------------------------------------ |
| `kind`               | `"nav:tip"`                 | Yes      | Action type                          |
| `config.id`          | string                      | Yes      | Unique tip identifier                |
| `config.title`       | string                      | Yes      | Accordion header text                |
| `config.description` | string                      | Yes      | Expanded body text                   |
| `config.href`        | string                      | No       | Optional CTA link URL                |
| `config.icon`        | string                      | No       | Icon (emoji or icon key)             |
| `config.external`    | boolean                     | No       | Open link in new tab                 |
| `config.category`    | string                      | No       | Category for grouping                |
| `showWhen`           | `DecisionStrategy<boolean>` | No       | Conditional visibility               |
| `notify`             | `{ title?, body?, icon? }`  | No       | Toast config for showWhen transition |
| `rationale`          | `{ why, confidence? }`      | No       | AI reasoning for recommendation      |

### Events Published

| Event              | When                                | Props                             |
| ------------------ | ----------------------------------- | --------------------------------- |
| `nav:toggled`      | User expands/collapses a tip        | `{ instanceId, tipId, expanded }` |
| `nav:tip_clicked`  | User clicks a tip's CTA link        | `{ instanceId, href, external }`  |
| `nav:tip_revealed` | `showWhen` transitions false → true | `{ tipId, title, body, icon }`    |

### Notify Watchers

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.

```json
{
  "id": "nav",
  "widget": "adaptive-nav:tips",
  "notifications": [
    {
      "on": "nav:tip_revealed",
      "title": "{{props.title}}",
      "body": "{{props.body}}",
      "icon": "🧭"
    }
  ],
  "props": {
    "expandBehavior": "single",
    "theme": "dark",
    "actions": [
      {
        "kind": "nav:tip",
        "config": {
          "id": "tip-analytics",
          "title": "Advanced Analytics",
          "description": "Unlock deeper insights with our analytics suite.",
          "href": "/analytics",
          "icon": "📊",
          "category": "Power User"
        },
        "notify": { "title": "New Tip", "body": "Advanced Analytics", "icon": "📊" },
        "showWhen": {
          "type": "rules",
          "rules": [
            {
              "conditions": [
                { "type": "event_count", "key": "link-clicks", "operator": "gte", "count": 3 }
              ],
              "value": true
            }
          ],
          "default": false
        }
      }
    ]
  }
}
```

## Navigation Actions

### `navigation:scrollTo`

Scrolls the viewport to bring an element into view.

| Property   | Type                    | Required | Default    | Description                                 |
| ---------- | ----------------------- | -------- | ---------- | ------------------------------------------- |
| `kind`     | `"navigation:scrollTo"` | Yes      |            | Action type                                 |
| `anchorId` | string                  | Yes      |            | Element selector                            |
| `behavior` | string                  | No       | `"smooth"` | `"smooth"`, `"instant"`, `"auto"`           |
| `block`    | string                  | No       | `"center"` | `"start"`, `"center"`, `"end"`, `"nearest"` |

### `navigation:navigate`

Navigates to a URL.

| Property | Type                    | Required | Default   | Description             |
| -------- | ----------------------- | -------- | --------- | ----------------------- |
| `kind`   | `"navigation:navigate"` | Yes      |           | Action type             |
| `url`    | string                  | Yes      |           | Destination URL         |
| `target` | string                  | No       | `"_self"` | `"_self"` or `"_blank"` |

**Note:** `javascript:` URLs are blocked for security.


---

# @syntrologie/adapt-overlays

Visual overlay capabilities including highlights, tooltips, badges, and pulse animations.

## Actions

### highlight

Creates a spotlight effect around an element with a scrim overlay.

| Property             | Type          | Required | Default     | Description                             |
| -------------------- | ------------- | -------- | ----------- | --------------------------------------- |
| `kind`               | `"highlight"` | Yes      |             | Action type                             |
| `anchorId`           | string        | Yes      |             | Element selector                        |
| `style.color`        | string        | No       | `"#5b8cff"` | Ring color                              |
| `style.scrimOpacity` | number        | No       | `0.55`      | Backdrop opacity 0-1 (set to 0 to hide) |
| `style.paddingPx`    | number        | No       | `12`        | Space around element                    |
| `style.radiusPx`     | number        | No       | `12`        | Ring corner radius                      |

```json
{
  "kind": "highlight",
  "anchorId": "#signup-button",
  "style": {
    "color": "#22c55e",
    "scrimOpacity": 0.4
  }
}
```

### tooltip

Shows a tooltip near an element with optional title, body, and CTA.

| Property             | Type        | Required | Default       | Description                            |
| -------------------- | ----------- | -------- | ------------- | -------------------------------------- |
| `kind`               | `"tooltip"` | Yes      |               | Action type                            |
| `anchorId`           | string      | Yes      |               | Element selector                       |
| `content.title`      | string      | No       |               | Tooltip heading                        |
| `content.body`       | string      | Yes      |               | Tooltip text                           |
| `content.cta.label`  | string      | No       |               | CTA button text (single-CTA shorthand) |
| `content.cta.action` | Action      | No       |               | Action to execute on CTA click         |
| `content.ctaButtons` | array       | No       |               | Multiple CTA buttons (see below)       |
| `trigger`            | string      | No       | `"immediate"` | `"immediate"`, `"hover"`, `"click"`    |
| `placement`          | string      | No       | `"top"`       | See placement options below            |

**Placement options:** `top`, `top-start`, `top-end`, `bottom`, `bottom-start`, `bottom-end`, `left`, `left-start`, `left-end`, `right`, `right-start`, `right-end`

**Single CTA (shorthand):**

```json
{
  "kind": "tooltip",
  "anchorId": "#pricing-toggle",
  "content": {
    "title": "Save 20%",
    "body": "Switch to annual billing to save on your subscription.",
    "cta": {
      "label": "Switch Now",
      "action": { "kind": "navigate", "url": "/billing?annual=true" }
    }
  },
  "placement": "bottom",
  "trigger": "immediate"
}
```

**Multiple CTAs (`ctaButtons`):**

Use `ctaButtons` for tooltips with multiple actions. Each button has:

- `label`: Button text
- `actionId`: Identifier published with the `action.tooltip_cta_clicked` event
- `primary`: Whether this is a primary button (default: false)

```json
{
  "kind": "overlays:tooltip",
  "anchorId": "[data-nav=\"features\"]",
  "content": {
    "title": "Learn About Features",
    "body": "We have answers about our feature set.",
    "ctaButtons": [
      { "label": "View Answer", "actionId": "faq:open:q-features", "primary": true },
      { "label": "Dismiss", "actionId": "dismiss" }
    ]
  },
  "trigger": "immediate",
  "placement": "bottom"
}
```

**Special actionId values:**

- `"dismiss"` — Destroys the tooltip immediately without publishing an event
- `"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.
- Any other value — Publishes `action.tooltip_cta_clicked` with the `actionId` in event props for custom handling

### badge

Adds a small badge indicator near an element.

| Property   | Type      | Required | Default       | Description                                                    |
| ---------- | --------- | -------- | ------------- | -------------------------------------------------------------- |
| `kind`     | `"badge"` | Yes      |               | Action type                                                    |
| `anchorId` | string    | Yes      |               | Element selector                                               |
| `text`     | string    | Yes      |               | Badge text (e.g., "NEW", "3")                                  |
| `position` | string    | No       | `"top-right"` | `"top-left"`, `"top-right"`, `"bottom-left"`, `"bottom-right"` |

```json
{
  "kind": "badge",
  "anchorId": "#inbox-icon",
  "text": "5",
  "position": "top-right"
}
```

### pulse

Adds a pulsing animation to draw attention.

| Property   | Type      | Required | Default | Description              |
| ---------- | --------- | -------- | ------- | ------------------------ |
| `kind`     | `"pulse"` | Yes      |         | Action type              |
| `anchorId` | string    | Yes      |         | Element selector         |
| `duration` | number    | No       | `2000`  | Animation duration in ms |

```json
{
  "kind": "pulse",
  "anchorId": ".notification-bell",
  "duration": 3000
}
```

### modal

Shows a centered modal dialog with optional CTA buttons.

| Property              | Type               | Required | Default | Description                                                   |
| --------------------- | ------------------ | -------- | ------- | ------------------------------------------------------------- |
| `kind`                | `"overlays:modal"` | Yes      |         | Action type                                                   |
| `content.title`       | string             | No       |         | Modal heading                                                 |
| `content.body`        | string             | Yes      |         | Modal text                                                    |
| `size`                | string             | No       | `"md"`  | `"sm"`, `"md"`, `"lg"`                                        |
| `blocking`            | boolean            | No       | `false` | Block page interaction                                        |
| `scrim.opacity`       | number             | No       | `0.6`   | Backdrop opacity 0-1                                          |
| `dismiss.onEsc`       | boolean            | No       | `true`  | Close on Escape key                                           |
| `dismiss.closeButton` | boolean            | No       | `true`  | Show close button                                             |
| `dismiss.timeoutMs`   | number             | No       |         | Auto-close after timeout                                      |
| `ctaButtons`          | array              | No       |         | Array of CTA buttons                                          |
| `waitFor`             | string             | No       |         | When to complete: `"dismissed"`, `"cta-click"`, `"timeout:N"` |

**CTA Button properties:**

- `label`: Button text
- `actionId`: Identifier for the action
- `primary`: Whether this is a primary button (default: false)

```json
{
  "kind": "overlays:modal",
  "content": {
    "title": "Welcome!",
    "body": "Thanks for signing up. Let us show you around."
  },
  "size": "md",
  "ctaButtons": [
    { "label": "Skip", "actionId": "skip" },
    { "label": "Start Tour", "actionId": "start", "primary": true }
  ],
  "waitFor": "cta-click"
}
```


---


---

## Surfaces

Surfaces are named slots where widgets can be mounted. Use the `mount_widget` action to render content into a surface slot.

### Static Slots

Fixed-position slots for common UI patterns:

| Slot | Position | Use Case |
|------|----------|----------|
| `drawer_right` | Right edge, full height | Settings panels, details |
| `drawer_left` | Left edge, full height | Navigation, filters |
| `drawer_bottom` | Bottom edge, full width | Action sheets, keyboards |
| `overlay_center` | Centered modal | Dialogs, confirmations |
| `overlay_corner_br` | Bottom-right corner | Chat widgets, help |
| `overlay_corner_bl` | Bottom-left corner | Notifications |
| `toast_top` | Top center | Success/error messages |
| `toast_bottom` | Bottom center | Snackbar notifications |

### Dynamic Slots

Slots that position content relative to anchors:

- **Inline Slots**: Render content inside an anchor element. Format: `inline:{anchorId}`
- **Adjacent Slots**: Render content positioned near an anchor element. Format: `adjacent:{anchorId}`

---

## Anchor Resolution

The `anchorId` field identifies which DOM element to target. Multiple formats are supported:

### CSS Selectors

| Format | Example | Matches |
|--------|---------|---------|
| Class | `".hero-title"` | `<h1 class="hero-title">` |
| ID | `"#signup-button"` | `<button id="signup-button">` |
| Attribute | `"[data-testid='cta']"` | `<button data-testid="cta">` |
| Tag + class | `"h1.page-title"` | `<h1 class="page-title">` |
| Nested | `".card .title"` | `<div class="title">` inside `.card` |

### Data Attributes (Recommended)

For stability, add `data-syntro-anchor` attributes to target elements:

```html
<h1 data-syntro-anchor="hero-title">Welcome</h1>
```

Then reference by the anchor name:

```json
{ "anchorId": "hero-title" }
```

---

## Decision Strategies

Control when adaptives activate using `DecisionStrategy`:

### Rules Strategy

```json
{
  "type": "rules",
  "rules": [
    {
      "conditions": [
        { "type": "page_url", "pattern": "/pricing*" },
        { "type": "viewport", "minWidth": 768 },
        { "type": "dismissed", "key": "promo", "inverted": true }
      ],
      "value": true
    }
  ],
  "default": false
}
```

### Condition Types

| Type | Parameters | Description |
|------|------------|-------------|
| `page_url` | `pattern` | URL matches glob pattern |
| `route` | `routeId` | Route ID matches |
| `anchor_visible` | `anchorId`, `state` | Anchor visibility |
| `event_occurred` | `eventName`, `withinMs?` | Recent event |
| `state_equals` | `key`, `value` | State matches |
| `viewport` | `minWidth?`, `maxWidth?`, `minHeight?`, `maxHeight?` | Viewport size |
| `session_metric` | `key`, `operator`, `threshold` | Metric comparison |
| `dismissed` | `key`, `inverted?` | Dismissal check |
| `cooldown_active` | `key`, `inverted?` | Cooldown check |
| `frequency_limit` | `key`, `limit`, `inverted?` | Frequency cap |

---

## Best Practices

### 1. Choose the Right Action Type

| Goal | Action |
|------|--------|
| Change text | `set_text` |
| Add visual indicator | `badge`, `pulse` |
| Show help text | `tooltip` |
| Draw attention | `highlight` |
| Add content | `insert_html` |
| Navigate user | `scroll_to`, `navigate` |
| Show UI panel | `mount_widget` + Surfaces |

### 2. Anchor Selection

- **Prefer stable selectors:** `[data-testid]`, IDs over classes
- **Test across states:** Element may not exist on all pages
- **Be specific:** Avoid selectors matching multiple elements

### 3. Reversibility

- Always store handles if you need to revert
- Use `applyBatch` for related changes (atomic rollback)
- Clean up on route changes

### 4. Performance

- Batch related actions together
- Use `validate()` to check actions before applying
- Avoid applying many actions simultaneously

### 5. Events

- Listen for `action.failed` to handle errors
- Track `action.applied` for analytics
- Use EventBus for cross-adaptive coordination
