# AI Element Picker

Most of the work in a traditional tag manager is wiring CSS selectors to event names. The AI Element Picker eliminates that step — you click an element on the customer site and Gemini proposes the selector, event name, and variable definitions.

## How It Works

1. From **Tag Manager > Preview Mode**, open the customer site with the preview cookie set.
2. Click **🎯 Element Picker** in the floating overlay.
3. The tracker enters element-picker mode: hovering highlights elements with a Shadow-DOM outline, clicking sends the element's HTML + parent context + DOM path to the admin UI via `postMessage`.
4. The admin UI calls `POST /p/:siteKey/preview/ai-element` with the captured payload.
5. The server calls Gemini with three tool declarations:
   - `propose_selector(strategy, value)` — preferred selector (`data-*` > `id` > unique class combo > nth-child fallback)
   - `propose_event_name(name, description)` — semantic event name like `add_to_cart_click`
   - `propose_variables(name, source, path)[]` — variables to extract from the element or its context
6. The proposed selector is **post-validated** against the captured HTML before being returned — if it doesn't uniquely match the original element, the server retries with a tighter strategy or returns `reason: 'selector_ambiguous'`.
7. You see the suggestion in the admin UI; clicking **Apply** creates a Tag + Trigger + the proposed Variables in the active workspace.

## Selector Strategy

Gemini is prompted to prefer selectors in this priority order:

1. **`data-*` attributes** (most stable across site refactors).
2. **`id` attribute** (only if it doesn't look auto-generated — UUIDs, hash-suffixed IDs, and obvious build-tool patterns are rejected).
3. **A unique combination of classes** (the smallest set that uniquely identifies the element on the page).
4. **`tag:nth-of-type(N)`** as a last resort (fragile — the picker explicitly warns when this is what gets used).

The model is also told to look at the surrounding DOM context (5 ancestor levels) so it can suggest selectors that survive sibling reordering.

## What Gets Sent to Gemini

Only what's needed to identify the element:

- The element's `outerHTML`, truncated at 20 KB. Larger elements return `413 element_html_too_large` — pick a more specific sub-element.
- The 5 nearest ancestor tags with their classes / ids (no inner text or other children).
- The page's `URL` and `title`.

No cookies, localStorage, or other PII leave the customer's browser. The picker runs entirely client-side until the user clicks **Apply**.

## Rate Limiting

The endpoint is wildcard-CORSed because preview cookies authenticate it, but it's still rate-limited:

- **12 calls per IP per minute per siteKey** (`PICKER_RATE_LIMIT_PER_IP = 12` in `snippetRoutes.js`).
- Rate-limit window is rolling.

The limit is intentionally low because each call costs ~5K Gemini input tokens (the element + context + tool declarations).

## When It Returns No Suggestion

The endpoint returns `422 { reason }` when:

| `reason` | Meaning |
| --- | --- |
| `element_required` | The picked element had neither `html` nor `tag` — likely a bug in the picker overlay. |
| `selector_ambiguous` | Gemini's proposed selector matched multiple elements on the page after post-validation. |
| `low_confidence` | Gemini wasn't confident enough to commit — the element is too generic (e.g. raw `<div>`). |
| `not_actionable` | The element looks like layout, not an interactive control. |

When you see `low_confidence` or `not_actionable`, pick a more specific element (e.g. the `<button>` inside the wrapper `<div>`).

## Cost & LLM Usage Logging

Every call is logged in the per-tenant `llmUsageModel` collection with the model name, input tokens, output tokens, and the action that triggered it (`'tag-manager-element-picker'`). This rolls into the same billing-limits stack as voice / chat agents — see the **Billing** docs for how the budget is enforced.

The model is configurable via `ANALYTICS_AI_MODEL` (default `gemini-3.8-flash`). Flash is intentionally chosen over Pro because the picker is interactive and latency-sensitive; tag suggestions from the *batch* path (Event Definitions inbox) use the same model but can afford Pro if you set `ANALYTICS_AI_MODEL_BATCH`.

## Privacy Considerations

The picker is admin-only via the preview cookie, but you should still be aware:

- The captured HTML can include text content from the customer's site. If the site contains PII in the DOM (e.g. an order-confirmation page), that text is sent to Gemini.
- Gemini API calls happen through Google's standard API with the project's own API key. Per Google's terms, prompts and outputs are not used to train Google's models by default for paid API keys.
- If you don't want Gemini to see anything from a particular page, don't pick elements on it — use the standard Tag Manager UI to write the selector by hand.

## When Not to Use It

- For very simple selectors (e.g. `#signup`, `[data-test="cart"]`), it's faster to type them in the Tag Manager UI directly.
- For elements that don't exist in the initial DOM (lazy-loaded modals, etc.), the picker can't see them until they're rendered. Trigger the lazy load first, then pick.
- For SPA frameworks that generate new class names on every build (CSS-in-JS, Tailwind JIT-stripped), the picker will recommend nth-of-type fallbacks — those are fragile. Ask the site team to add a `data-track-id="..."` attribute and re-pick.
