> ## Documentation Index
> Fetch the complete documentation index at: https://docs.callers.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Page Guide

> Let your web call agent see the page the visitor is on and highlight, scroll to or open elements while it talks, with form values and private areas never shared.

Page Guide lets the agent see a text summary of the page your widget is on, and point at it while it speaks. When a visitor asks "where do I change my plan?", the agent answers from what is actually on screen: the page scrolls to the right button and a soft ring marks it as the agent says "click **Upgrade to Pro**". If you allow it, the agent can also open a link on your site.

Use it for product tours, support on complex pages, and any flow where "it's the third button on the left" isn't good enough.

> [!NOTE]
>
> Page Guide is in early access. Behavior and limits may change, and Callers may ask for feedback on how your agent uses it.

```html theme={"dark"}
<script src="https://cdn.callers.ai/sdk.js" data-agent="YOUR_AGENT_ID" data-page-guide="true"></script>
```

![A pricing page with the Upgrade to Pro button marked by a soft Page Guide ring and the Callers call window open on the right](https://mintcdn.com/voxiaai/dmHXPB02yobC5Lfr/images/documentation/sdk/page-guide-spotlight.png?fit=max\&auto=format\&n=dmHXPB02yobC5Lfr\&q=85\&s=cd44fc2d43c173f4a597bfd8c7a13951)

While the agent says “click Upgrade to Pro”, a thin ring and a soft glow mark the button, and the rest of the page is shaded very lightly. The call window stays open beside it.

> **[Try it with your agent](https://sdk-playground.callers.dev/?pageGuide=true)**
>
> Opens the playground with Page Guide on. Start a test call and ask about the sample site.

> **[Watch the demo without a call](https://sdk-playground.callers.dev/page-guide)**
>
> A simulated reply plays highlight and scroll actions against a sample billing page. No credentials, no audio.

## What the agent can do

| Action | What the visitor sees | Allowed by default |
| - | - | - |
| Read | Nothing. The agent receives a text summary of the page. | Yes |
| `highlight` | The element is scrolled into view and marked in the [highlight style](#highlight-styles) you choose, by default a thin ring with a light shade over the rest of the page | Yes |
| `scroll` | The page scrolls the element to the center of the view | Yes |
| `navigate` | The browser opens a link that is on the page and stays on your site. Only real links, never buttons or downloads | No, list it in `actions` |
| `clear` | Any highlight is removed | Always |

The agent never clicks, types or submits anything. The visitor always does that themselves. Actions are timed to the agent's words and stop when the visitor starts speaking. The agent can only point at elements it was shown in a summary, and can only open links that appear in one. It never highlights an element the visitor can't see: an element that is hidden, or covered by something floating over the page, is reported instead of drawn.

You don't change the agent's script for this. Turning Page Guide on in the SDK gives the agent the ability for that call, and it learns the page format from the summary itself.

## How it works

<ol />

From the page to the agent and back

### What the summary contains

The summary is built from the DOM in the visitor's browser, never from a screenshot. It is a plain-text list in the style of an accessibility tree: one line per element that matters, each with a short stable ref the agent can point at.

```text theme={"dark"}
url: https://app.northwind.example/billing
title: Plans and billing
viewport: 1440x900
e8 heading "Plans and billing" (in view, level 1)
e15 region "Pro plan" (in view)
e19 button "Upgrade to Pro" (in view)
e14 button "Current plan" (in view, disabled)
e27 textbox "Card number" (private field, filled)
e24 link "Talk to sales" (in view) -> /billing#contact-sales
e6 link "Integrations" (in view, external)
e33 text "Dashboards | 3 | 3"
e37 button "Download invoices"
(12 more elements not listed)
```

What is captured:

| Kind | Elements | Named by |
| - | - | - |
| Interactive | Buttons, links with an `href`, text fields, search boxes, checkboxes, radios, switches, comboboxes and selects, list boxes and options, tabs, menu items, sliders, and anything with a matching `role` | `aria-label`, `aria-labelledby`, the form label, `alt`, the placeholder, the visible text, `title`, or a button's `value` |
| Structure | Headings (with their level), `nav`, `main`, `form`, `dialog`, labeled `section`s, images with `alt`, iframes | Their label only, never the text inside them |
| Text | Paragraphs, list items, table rows (cells joined with a vertical bar, as in the example above), definition terms, block quotes, figure captions | Their text, up to 100 characters |

Each line also notes whether the element is **in view**, and for fields whether they are `filled` or `empty`, `checked` or `not checked`, `required`, and `disabled`. Same-site links show the path they open; links to other sites are marked `external` without their address. Iframes are listed as `(contents not shared)`.

What is not captured:

* Anything `hidden`, `inert`, `aria-hidden="true"`, `display: none` or `visibility: hidden`.
* The insides of interactive elements (a button is one line, whatever it contains).
* Closed shadow roots (open ones are walked), the contents of iframes, canvas, video and images without `alt`.
* The widget itself and the spotlight overlay.
* Everything excluded by the [privacy rules](#privacy-and-security) below.

### Limits and ordering

| Limit | Value |
| - | - |
| Elements listed | `maxElements`, default **150**, allowed 1–300 |
| Element name / text | 80 / 100 characters, cut with `…` |
| Page title / URL | 120 / 300 characters |
| Whole summary | 8,000 characters; whole lines are dropped past that and the summary says how many |
| Elements walked per capture | 20,000, in page order; the summary says if the page was larger |

When a page has more elements than fit, the summary keeps what the visitor can see first, then what they can act on (interactive before structure before text), and restores page order afterwards. The URL is sent as origin and path only; query strings and hashes are left out because they can carry personal data.

### When summaries are sent

* A summary is sent when the call connects, and again after the page changes (content updates, scrolling, resizing, in-app navigation, hash changes) and has been quiet for about **1.2 seconds**. A page that never settles, such as one with a ticker, is still reported 5 seconds after its first unsent change.
* Sends are at least **3 seconds** apart, and an unchanged summary isn't resent. Scrolling alone changes which elements are "in view", so only a scroll that changes what the visitor sees produces a new summary.
* A failed send is retried up to 3 times. Failures are reported to `onError`.
* Sends cost the agent nothing: the summary reaches the agent only on turns where the visitor asks about the page, and only the latest one. Ordinary conversation isn't slowed down or made longer.

The SDK posts summaries to `POST https://api.callers.ai/campaigns/{agentId}/web-call/page-context` with the same token or domain check as the rest of the call. The endpoint accepts at most 16,000 characters, twice the widget's cap, so a summary is never rejected for size. You don't call it yourself.

### How the agent points

The agent writes short tags inside its reply, before the words they belong to: `To upgrade, [highlight:e19] click Upgrade to Pro.` Callers strips the tags before text-to-speech and before the transcript, so visitors never hear or read them, and publishes each sentence to the widget as its audio starts, with the actions and their position in the sentence.

The widget then runs each action when its words are heard:

* A sentence starts playing about 250 ms after it arrives, or when the previous sentence ends, whichever is later.
* Within a sentence, the action fires at the character timings of the agent's voice when the provider returns them (within about one word), or at 15 characters per second otherwise (within about half a second).
* A new reply drops the previous reply's pending actions. When the visitor interrupts the agent, the rest of the reply's actions are canceled.

Refs are stable for the whole call: an element keeps its `e12` through later summaries, so a tag the agent wrote against a slightly older summary still points at the same element.

### The spotlight

The highlight is drawn in its own shadow root on `<body>`, so your page's styles can't restyle it and it can't restyle your page. It never takes pointer events: the visitor can click the highlighted element straight away. It follows the element through scrolling, resizing and layout changes, and disappears if the element is removed from the page. Its accent color and font come from the widget's theme (the primary color and font) unless you set `color`, and it sits one layer under the widget so the call window is never shaded.

By default it is gentle: a thin ring with a soft glow, one short glow when it arrives, and a very light shade over the rest of the page that fades out around the element instead of cutting a hard hole. A small light label shows the element's name, but only when the element doesn't already show that name itself, so a button that reads "Upgrade to Pro" isn't labeled twice. Elements that fill most of the screen get the ring without the shade.

Scrolling is smooth unless the visitor prefers reduced motion; then the page jumps to the element and the arrival glow is off. Visitors who ask for more contrast get a stronger ring and shade, and in forced-colors mode (such as Windows High Contrast) the ring is drawn as a system-colored outline.

#### Highlight styles

| `highlight` | What it looks like |
| - | - |
| `soft` (default) | A thin ring with a soft glow, one arrival glow, and a very light shade over the rest of the page |
| `glow` | A soft halo around the element. Nothing else on the page changes |
| `outline` | A clean ring only |
| `spotlight` | A stronger ring, two arrival glows, and a darker shade that puts the element in a spotlight |
| `none` | Nothing is drawn. The page still scrolls to the element |

Fine-tune any style with `dim`, `color` and `label` (see the [configuration reference](#configuration-reference)). `dim` sets how dark the shade is for `soft` and `spotlight`; the other styles have no shade.

#### Making sure the visitor can see it

Before drawing, the SDK checks that the element is really visible at that spot on the screen:

* **Fixed or sticky bars**, such as a site header, a cookie banner or a chat bubble: the page is scrolled so the element lands in the clear part of the screen, not underneath them.
* **Hidden elements**, such as a carousel slide that isn't showing or the contents of a closed accordion, aren't drawn. The action reports `hidden`.
* **Covered elements**, under something floating over the page wherever it scrolls (an open popup, for example), are scrolled to but not drawn. The action reports `covered`.
* If content loading above the element pushes it off screen after the scroll, the SDK scrolls once more. If a popup opens over the element while it is highlighted, the highlight is removed.

### Navigation

`navigate` is off by default. When you allow it, the agent can open a link from the summary, never a free-form URL, and only when the link stays on the same origin over `http` or `https`. Only real links (`<a href>` and image-map `<area>` elements) can be opened. Buttons, links with a `download` attribute, `javascript:` links, bare `#` anchors and other sites are refused with the outcome `not-allowed`, so opening something never submits a form or starts a download.

When `navigate` isn't allowed but `highlight` is, a request to open a link highlights the link instead, so the visitor still sees where to go.

On a multi-page site, the browser loads the new page, **which ends the call**: the SDK is reloaded with the page. On a single-page app, pass `onNavigate` and route in place so the call survives (see the [React example](#react-with-react-router)). Keep `navigate` off on multi-page sites unless a dropped call is acceptable.

### The visitor notice

While Page Guide is on, the call window shows a one-line notice before and during the call: **The agent can see this page** by default, with a tooltip explaining that what the visitor types in forms is never shared. During the call the line has a **Stop** button. Stopping cancels pending actions, removes any highlight, stops sending summaries and tells the agent it can no longer see the page; the line then reads **Page sharing is off** with a **Share** button to turn it back on.

Change the text with `notice`. Keep it honest and short; the tooltip is fixed.

![Call window header showing the text The agent can see this page with a Stop link](https://mintcdn.com/voxiaai/UGuY0UNIphM-6pUX/images/documentation/sdk/page-guide-notice.png?fit=max\&auto=format\&n=UGuY0UNIphM-6pUX\&q=85\&s=22381d3ebc5db44662aee07e9257c1a4)

The notice under the call controls tells the visitor the agent can see the page. Stop turns page sharing off for the rest of the call.

## Turn it on

```html Script tag theme={"dark"}
<script
  src="https://cdn.callers.ai/sdk.js"
  data-agent="YOUR_AGENT_ID"
  data-page-guide="true"
  data-page-guide-actions="highlight,scroll,navigate"
  data-page-guide-hide=".account-menu,#billing"
  data-page-guide-highlight="soft"
  data-page-guide-color="#0f766e"
></script>
```

```javascript Programmatic theme={"dark"}
import { WebCall } from '@callers/web-sdk';

const webCall = new WebCall({
  token,
  pageGuide: {
    actions: ['highlight', 'scroll', 'navigate'],
    hideSelectors: ['.account-menu', '#billing'],
    highlight: 'soft',
    color: '#0f766e',
    maxElements: 200,
    notice: 'Our assistant can see this page to help you',
    onNavigate: (path) => router.push(path), // single-page apps
    onPageAction: (action, outcome) => console.log(action.type, action.ref, outcome),
  },
});
```

`pageGuide: true` (or `data-page-guide="true"` alone) uses the defaults: highlight and scroll, 150 elements, the standard notice and privacy rules. The script tag can set `actions`, `hideSelectors` and the highlight options (`highlight`, `dim`, `color`, `label`); the other options need the programmatic form.

## Configuration reference

**pageGuide** `boolean | PageGuideConfig` (default: false)

`true` turns Page Guide on with the defaults. An object sets the options below. Script tag: `data-page-guide="true"`.

### `PageGuideConfig`

**actions** `PageActionType[]` (default: \['highlight', 'scroll'])

What the agent may do: any of `highlight`, `scroll` and `navigate`. `clear` is always allowed. Script tag: `data-page-guide-actions`, comma-separated; unknown names are dropped.

**hideSelectors** `string[]`

CSS selectors for areas never shared with the agent, including everything inside them. `[data-callers-private]` is always hidden. An invalid selector is ignored rather than breaking the capture. Script tag: `data-page-guide-hide`, comma-separated.

**highlight** `HighlightStyle` (default: 'soft')

How the element the agent points at is shown: `soft`, `glow`, `outline`, `spotlight` or `none`. See [Highlight styles](#highlight-styles). Script tag: `data-page-guide-highlight`; unknown values fall back to `soft`.

**dim** `number`

How dark the shade over the rest of the page is, from `0` (none) to `0.5`. Values outside the range are clamped. Applies to `soft` (default `0.1`) and `spotlight` (default `0.28`); the other styles have no shade. Script tag: `data-page-guide-dim`.

**color** `string`

The ring and glow color, any CSS color. Defaults to the widget theme's primary color. Script tag: `data-page-guide-color`.

**label** `boolean` (default: true)

`false` never shows the name label next to the element. Even when `true`, the label appears only when the element doesn't already show its name. Script tag: `data-page-guide-label="false"`.

**maxElements** `number` (default: 150)

How many elements a summary lists, 1–300. Values outside the range are clamped. Larger pages keep what is in view and interactive first.

**notice** `string` (default: The agent can see this page)

The line shown in the call window telling visitors the agent can see the page.

**onNavigate** `(path: string) =\> void`

For single-page apps. Receives the path (with query string and hash) of the link the agent wants to open; route to it in place. Without it, the browser loads the new page, which ends the call.

**onPageAction** `(action: PageAction, outcome: PageActionOutcome) =\> void`

Called for every action the agent takes, with its outcome. Use it for analytics or to react in your own UI.

### Types

```typescript theme={"dark"}
import type { PageGuideConfig, PageAction, PageActionType, PageActionOutcome, HighlightStyle } from '@callers/web-sdk';

type PageActionType = 'highlight' | 'scroll' | 'navigate' | 'clear';

type HighlightStyle = 'soft' | 'glow' | 'outline' | 'spotlight' | 'none';

interface PageAction {
  type: PageActionType;
  ref?: string;        // the element's ref in the summary, e.g. 'e19'; absent for clear
  charOffset: number;  // where in the sentence the action belongs
}

type PageActionOutcome =
  | 'done'          // the action ran
  | 'not-allowed'   // the action isn't in `actions`, the link leaves your site, or it isn't a real link
  | 'not-found'     // the element is no longer on the page
  | 'hidden'        // on the page but not visible, e.g. a carousel slide that isn't showing
  | 'covered';      // under something floating over the page wherever it scrolls
```

## Runtime API

### `webCall.getPageContext()`

Returns the exact summary the agent would receive right now, as text, or `null` when Page Guide is off. Call it on any page to audit what is shared, write tests for your `hideSelectors`, or debug why the agent can't find a button.

```javascript theme={"dark"}
console.log(window.Callers.widget.getPageContext());
// With a programmatic WebCall:
console.log(webCall.getPageContext());
```

### Transcript messages

Transcript messages from `onTranscriptChanged` and `onTranscriptMessage` carry the agent's sentences with their actions, after the tags were stripped from the text:

```javascript theme={"dark"}
new WebCall({
  token,
  pageGuide: true,
  onTranscriptChanged: (messages) => {
    const latest = messages.filter((m) => m.role === 'assistant').at(-1);
    latest?.llmResponseSentences?.forEach((sentence) => {
      console.log(sentence.textWithoutEmotions, sentence.pageActions);
      // "click Upgrade to Pro." [{ type: 'highlight', ref: 'e19', charOffset: 0 }]
    });
  },
});
```

The summaries themselves are not streamed back in the transcript.

## Privacy and security

Every rule below is enforced in the visitor's browser, before anything leaves it. The summary is plain text you can read with `getPageContext()`.

### What never leaves the browser

* **Form values.** Fields are described by their label and state (`filled`, `empty`, `checked`), never by what is in them. The capture never reads a field's value into the summary.
* **Sensitive fields** are additionally marked `private field`: password and hidden inputs, fields with `autocomplete` of `cc-*`, `one-time-code`, `new-password` or `current-password`, and fields whose name, id, `aria-label` or placeholder mentions a password, card, CVV/CVC, SSN, IBAN, account or routing number, secret, token or PIN.
* **Emails and long numbers** in page text, headings, titles and element names are replaced with `(email hidden)` and `(number hidden)` before anything is cut short, so a truncated email can't slip through. Dates and prices have too few digits to match.
* **Private areas.** Anything inside `[data-callers-private]` or your `hideSelectors` is never walked. If an element's own name would be read from text that contains a private area, that element is left out too, while its other children are still listed.
* **Hidden content**, the widget, the spotlight overlay, query strings and hashes in the URL, and the addresses of external links.

Mark private areas in your HTML:

```html theme={"dark"}
<section data-callers-private>
  <h2>Saved payment methods</h2>
  …
</section>

<nav class="account-menu">…</nav>
<!-- plus hideSelectors: ['.account-menu'] -->
```

### What the agent can't do

* It can only point at elements that appeared in a summary this widget sent, so it can't reach into hidden areas even by guessing a ref.
* It can't click, type, submit, or open a URL it made up. `navigate` reaches only a same-site link from the summary, and only when you allow navigation.
* Page text reaches the agent as untrusted content. Instructions written on the page can't give the agent new abilities: the worst an injected instruction can do is highlight an element or open a same-site link, if you allowed it.

### Server side

Summaries are accepted only for calls that opted in (the SDK marks the call when Page Guide is on), so a page can't push context into a call that didn't ask for it. The agent reads the latest summary on turns about the page; summaries are not streamed back to the browser in the transcript.

> [!NOTE]
>
> Page text is sent to Callers and processed by the language model behind your agent, like the rest of the conversation. Mark anything personal with `data-callers-private`, even sections with no form fields, and review `getPageContext()` on pages that show account data.

## Single-page apps

In a SPA, in-app navigation changes the DOM without a page load. The SDK watches DOM changes, `popstate` and `hashchange`, so a new route produces a new summary without any extra code. Two things need your attention:

1. **`onNavigate`.** If you allow `navigate`, route in place so the call survives. Without it, `location.assign` loads the page and the call ends.
2. **Stable `WebCall`.** Create the `WebCall` once, outside the component tree that re-renders on route changes, so the call and the page refs survive navigation.

### React with react-router

```jsx theme={"dark"}
import { useEffect, useRef } from 'react';
import { useNavigate } from 'react-router-dom';
import { WebCall } from '@callers/web-sdk';

export function CallersWidget({ token }) {
  const navigate = useNavigate();
  const navigateRef = useRef(navigate);
  navigateRef.current = navigate; // always the latest navigate, without recreating the WebCall

  useEffect(() => {
    const webCall = new WebCall({
      token,
      pageGuide: {
        actions: ['highlight', 'scroll', 'navigate'],
        hideSelectors: ['[data-account]'],
        onNavigate: (path) => navigateRef.current(path), // '/billing?tab=invoices#history'
        onPageAction: (action, outcome) => {
          if (outcome !== 'done') console.warn('page action', action.type, action.ref, outcome);
        },
      },
    });
    return () => webCall.destroy();
  }, [token]);

  return null;
}
```

Mount `CallersWidget` once at the app root, inside the router so `useNavigate` works. For Next.js, Vue Router or Angular, pass the equivalent of `router.push(path)` to `onNavigate`.

## Tips for pages that guide well

* Give buttons and links clear, visible text or `aria-label`s. The agent can only point at what it can name, and the spotlight label shows that name to the visitor.
* Use real `<button>` and `<a href>` elements, or `role="button"` / `role="link"`. A clickable `<div>` without a role is listed as text at best.
* Label landmarks (`<nav aria-label="Account">`, `<section aria-label="Pro plan">`). Landmarks are named by their label only, so an unlabeled section contributes nothing.
* Keep the key controls of a page within the first 150 elements, or raise `maxElements` (up to 300) on dense pages.
* Hide account details, admin panels and anything personal with `data-callers-private`, even when they contain no form fields.
* Check `getPageContext()` on every template once. If a button is missing, it's hidden, inside an iframe or a closed shadow root, or has no name.

## Try it in the playground

**1. Play the demo without a call**

Open [sdk-playground.callers.dev/page-guide](https://sdk-playground.callers.dev/page-guide). The left panel shows **What the agent sees**: the live summary of the sample billing page, including `private field` markers on the card fields, redacted contact details, and nothing from the `data-callers-private` section.

**2. Play a reply**

Click **Play reply (silent)**. The sample reply's tags run through the real tag parser and scheduler: a caption shows the words being "spoken" while **Upgrade to Pro** and then **Download invoices** are highlighted on the real timing. **Page actions** logs each action and its outcome. Edit the reply text to try your own tags, or **Reset sample**.

**3. Interrupt and stop sharing**

**Visitor interrupts** drops the rest of the reply's actions. **Stop** in the call window's notice stops sharing: highlights clear and the **Page actions** log records it.

**4. Try it on a real call**

Open [sdk-playground.callers.dev/?pageGuide=true](https://sdk-playground.callers.dev/?pageGuide=true), enter your **Agent ID** and secret, and start a test call. Ask the agent about the sample site on the right ("where do I upgrade?"). **Page guide** is also a switch in the playground's **Behavior** step, and `pageGuide=true` is kept in **Share demo** links.

![The Page Guide demo page with the page summary and simulated reply on the left and a highlighted button on the sample page](https://mintcdn.com/voxiaai/UGuY0UNIphM-6pUX/images/documentation/sdk/page-guide-demo.png?fit=max\&auto=format\&n=UGuY0UNIphM-6pUX\&q=85\&s=9838bd8037ca5ba844ed9ee5246ccc46)

The Page Guide demo: what the agent sees (left), the simulated reply with its action tags, and the highlight running on the sample page.

## In the dashboard

Page Guide has no settings in the dashboard yet: it is turned on per embed, in the script tag or the SDK config, by whoever installs the widget. The embed snippet under **Channels** > **Widget** contains only `data-agent` and your variables; add `data-page-guide="true"` and the other attributes yourself. Dashboard test calls don't use Page Guide; use the [playground](https://sdk-playground.callers.dev/?pageGuide=true) to test it against your agent.

## Troubleshooting

| Symptom | Likely cause | What to do |
| - | - | - |
| No notice in the call window | Page Guide isn't on: `data-page-guide` must be exactly `"true"` | Check the attribute, or `pageGuide: true` in code |
| The agent says it can't see the page | Sharing was stopped, or the first summary hasn't been sent yet | Click **Share** in the notice; summaries go out when the call connects and after the page settles |
| The agent can't find a button that's on screen | The element has no name, is inside a closed shadow root or iframe, or is beyond `maxElements` | Run `getPageContext()`; add a label or `aria-label`, or raise `maxElements` |
| `onPageAction` reports `not-found` | The element was removed or re-rendered since the summary | Keep element identity stable across renders (same DOM node), or accept that the next summary fixes it |
| `onPageAction` reports `not-allowed` for `navigate` | `navigate` isn't in `actions`; the link leaves your site; it's a `#` / `javascript:` link or has `download`; or it's a button rather than a link | Allow `navigate`, and use real `<a href>` elements with same-site addresses for pages the agent should open |
| `onPageAction` reports `hidden` | The element is on the page but not visible: inside a closed accordion or menu, a carousel slide that isn't showing, or zero size | Expected; the next summary marks what is in view |
| `onPageAction` reports `covered` | Something floating over the page, such as a popup or a large fixed panel, hides the element wherever it scrolls | Close the overlay, or make sure fixed elements leave room for the content |
| The call drops when the agent opens a link | Multi-page navigation reloads the page | Pass `onNavigate` in a SPA; otherwise remove `navigate` from `actions` |
| The highlight is under my header or modal | Your header or modal has a higher `z-index` than the widget | The SDK scrolls elements clear of fixed and sticky bars, but the ring itself sits one layer below the widget (`z-index` 9998 by default). Keep headers and modals below that, or the ring and label draw behind them |
| The highlight is too strong or too faint | The style or shade doesn't suit your page | Pick another `highlight` style, or set `dim` and `color` |
| Private content appears in the summary | The selector in `hideSelectors` is invalid or doesn't match | Test the selector with `document.querySelector`; invalid selectors are ignored silently |
| Highlights fire late or early | The voice provider returned no timings, so actions use a 15 chars/second estimate | Expected without timings; accuracy is about half a second |
| `onError` receives `Failed to send page context` | The send was rejected: an origin not on the allowed list, an expired token, or a summary over 16,000 characters | Fix the domain or token; size errors can't happen with the SDK's own cap |

## FAQ

> **Can the agent fill in forms or click for the visitor?**
>
> No. Page Guide is pointing only: highlight, scroll and, if allowed, open a same-site link. The visitor does the clicking and typing.

> **Does Page Guide see what the visitor typed?**
>
> No. A field is reported as `filled` or `empty`, never with its contents, and emails and long numbers in page text are redacted. Use `getPageContext()` to see the exact text.

> **Does it slow down the conversation or cost more?**
>
> Sends are free and happen in the background. The summary is added to the agent's context only on turns where the visitor asks about the page, so other turns are unchanged.

> **Does it work with avatars?**
>
> Yes. On avatar calls the voice's character timings are available, so page actions land within about a word of the agent's speech. See [Avatars](https://docs.callers.ai/sdk/avatars.md).

> **Does it work on text conversations?**
>
> Page Guide starts when the call connects, in voice and text conversations alike, so the agent can see the page either way. Highlights are timed to the agent's spoken sentences, which makes voice calls where it shines.

> **Can I change the look of the spotlight?**
>
> Yes. Choose a `highlight` style (`soft`, `glow`, `outline`, `spotlight` or `none`), and adjust the shade with `dim`, the color with `color`, and the name label with `label`. The font comes from the widget theme. Try the styles with the playground's **Highlight** option, available when **Page guide** is on.

> **Can the agent see other tabs or pages?**
>
> No. Only the page the widget is on, and only while the visitor shares it.

> **Does the agent need a different script?**
>
> No. The ability is attached to the call when Page Guide is on. Your script can still tell the agent what to help with, for example "guide visitors to the right plan".

## Related

> **[Avatars](https://docs.callers.ai/sdk/avatars.md)**
>
> A talking face that points at the page while it speaks.

> **[Configuration](https://docs.callers.ai/sdk/configuration.md#page-guide)**
>
> All options and script-tag attributes.

> **[Playground](https://docs.callers.ai/sdk/playground.md)**
>
> Try Page Guide and the demo page.

> **[Installing the widget](https://docs.callers.ai/documentation/widget-agents/installing-the-widget.md)**
>
> The no-code setup in the dashboard.

> **[Events and transcript](https://docs.callers.ai/sdk/events-and-transcript.md)**
>
> Follow the call and its sentences from your code.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.