# Page awareness

The headline reason to use the NPM package over the script tag: the agent can **see** your page and **fill** your forms. Two primitives make this work — `extractPageContext` (which walks the DOM automatically) and `LiveLayerRegion` (for curated content sections). Forms need NO special markup — every `<form>` is auto-discovered.

## How it works (30 seconds)

1. The agent emits `request_page_context` over its data channel when it needs to "look around."
2. The widget walks the DOM, extracts headings, links, visible text, form fields, and any explicitly marked regions.
3. The walked snapshot is sent back to the agent as JSON.
4. When the agent emits a `fill_field` or `submit_form` command, the widget targets the right element by ID.

The DOM walk respects two opt-out signals: `data-ll-private="true"` excludes a subtree, and password fields are always excluded.

## `extractPageContext`

The default DOM walker. You can call it directly or override it via the `getPageContext` prop.

```tsx
import { extractPageContext } from "@livelayer/react";

const context = extractPageContext({
  // Optional extras passed straight through
  currentUser: "user_abc",
  cartTotal: 12.99,
});
// → { headings, links, visibleText, formFields, regions, extras }
```

For most apps, the default extraction is enough. Override it when:

- Your app's "real" state lives in a store, not the DOM (e.g., virtualized lists where most items aren't rendered).
- You want to feed the agent richer context (current route's title, user role, etc.).
- The default walk is too slow on a complex page.

```tsx title="custom-context.tsx"
import { AvatarWidget } from "@livelayer/react";

<AvatarWidget
  agentId="agt_abc123"
  getPageContext={async () => ({
    headings: [{ level: 1, text: "Dashboard" }],
    visibleText: useDashboardStore.getState().summarizeForAgent(),
    formFields: [],
    links: [],
    regions: [],
  })}
  pageContextExtras={{
    currentUser: useUserStore.getState().email,
    plan: useUserStore.getState().plan,
  }}
/>
```

## `<LiveLayerRegion>` — mark important regions

Wrap a region of your UI in `<LiveLayerRegion>` to give it a stable name the agent can address.

```tsx title="region.tsx"
import { LiveLayerRegion } from "@livelayer/react";

export function PricingPage() {
  return (
    <main>
      <LiveLayerRegion name="hero" description="Top hero with primary CTA">
        <h1>Pricing</h1>
        <p>Simple, transparent.</p>
      </LiveLayerRegion>

      <LiveLayerRegion name="plans" description="Pricing tiers">
        <!-- omitted: PlanCard -->
        <!-- omitted: PlanCard -->
        <!-- omitted: PlanCard -->
      </LiveLayerRegion>

      <LiveLayerRegion name="faq" description="Frequently asked questions">
        <!-- omitted: FaqList -->
      </LiveLayerRegion>
    </main>
  );
}
```

Now the agent's view of the page includes named regions:

```json
{
  "regions": [
    { "name": "hero",  "description": "Top hero with primary CTA",  "text": "Pricing. Simple, transparent." },
    { "name": "plans", "description": "Pricing tiers",              "text": "Starter $9/mo... Pro $29/mo... Enterprise..." },
    { "name": "faq",   "description": "Frequently asked questions", "text": "..." }
  ]
}
```

The agent can then say "scroll to plans" and the widget knows where to scroll.

## Agent-fillable forms

Just write regular HTML. Every `<form>` on the page is auto-discovered and surfaced to the agent. The agent can fill, validate, and submit it — or run a guided voice sub-conversation through it via `collect_from_page`.

```tsx title="form.tsx"
export function ContactForm() {
  const [name, setName] = useState("");
  const [email, setEmail] = useState("");
  const [message, setMessage] = useState("");

  return (
    <form onSubmit={() => sendMessage({ name, email, message })}>
      <label>Your name
        <input name="name" value={name}
               onChange={(e) => setName(e.target.value)} required />
      </label>
      <label>Email address
        <input name="email" type="email" value={email}
               onChange={(e) => setEmail(e.target.value)} required />
      </label>
      <label>Message
        <textarea name="message" value={message}
                  onChange={(e) => setMessage(e.target.value)} />
      </label>
      <button type="submit">Send</button>
    </form>
  );
}
```

What this enables:

- Visitor: "Help me fill this out — my name is Jordan, email jordan@acme.com, message: When are you launching?"
- Agent: calls `fill_form` with all three values at once, then `submit_form` when the user confirms.
- OR — for a voice-first walk-through — the agent calls `collect_from_page` and asks each question one at a time with per-kind normalization.

The agent infers each field's label from the wrapping `<label>` / `aria-label` / `placeholder`, and infers the kind from the `type=` attribute. Pass `<!-- omitted: AvatarWidget -->` to receive the typed payload when the agent finishes a guided collection.

## Opt-out — keep forms or fields invisible

```tsx
<form data-ll-skip>...</form>           // exclude an entire form
<input data-ll-private />               // exclude a single input
```

And, always-on (browser-native) privacy guards: `type="password"`, `autocomplete="cc-*"`, and `autocomplete="off"` are ALWAYS excluded — you don't need to mark them.

## Disambiguation hint

When a page has multiple forms and the LLM can't tell them apart, add `data-ll-intent`:

```tsx
<form data-ll-intent="request a demo">...</form>
<form data-ll-intent="newsletter signup">...</form>
```

## `data-ll-private="true"` — opt-out subtree

Mark any subtree as off-limits to context extraction:

```tsx title="private.tsx"
<section data-ll-private="true">
  {/* This entire section is invisible to the agent's page context */}
  <!-- omitted: CreditCardField -->
  <!-- omitted: SocialSecurityField -->
</section>
```

Use this for:

- Sensitive PII (medical records, financial details, government IDs)
- Internal admin UI you don't want the agent surfacing
- Diagnostic / debug panels

Password fields (`<input type="password">`) are excluded automatically — you don't need to mark them.

## Capability gating

Page awareness is gated behind capabilities. To allow form filling, include `fill_forms` and `submit_forms`:

```tsx
<!-- omitted: AvatarWidget -->
```

Without the right capabilities, the agent can request page context but won't be able to act on what it sees. See [`AvatarWidget` capabilities](/docs/develop/npm/react/avatar-widget#capabilities).

## Performance notes

- The default DOM walk is cached. Successive `request_page_context` calls within the same route hit the cache.
- The cache is invalidated when `pathname` changes (which is why passing it for SPAs matters).
- For pages over ~5000 elements, write a custom `getPageContext` that returns a structured summary instead of walking the whole DOM.
- Use `clearPageContextCache()` (exported) if you mutate the page meaningfully without a route change.

## Read next

- [Routing](/docs/develop/npm/react/routing) — `pathname`, `showOn`, `hideOn` for SPAs
- [`AvatarWidget` props](/docs/develop/npm/react/avatar-widget) — full reference for `getPageContext`, `pageContextExtras`, `getRoutes`, `capabilities`
