# Observe

Subscribe to component changes across a page or within one part of it.

`observe` reports changes from ui-sdk components in one stream. Use it to keep an activity log, update a preview, or see which actions came from a person or an agent.

It works with every framework. Import it from `@ui-sdk/core/observe`; the framework packages also re-export it.

## Listen for Changes

Start observing in your browser entry point, before rendering the app. This lets the observer register each component as it's created:

```ts
import { observe } from "@ui-sdk/core/observe"

const stop = observe((change) => {
  console.log(change.label, change.key, change.previous, change.value)
})
```

For example, choosing a different visibility setting might report `Visibility`, `value`, `private`, and `internal`.

Call the returned function when you no longer need the subscription:

```ts
stop()
```

Register outside component render functions. An effect can run after child components have already been created, so use the app's browser setup when you need to observe the whole page.

## Read a Change

Each notification is an `ObservedChange`:

| Field               | What It Tells You                                                           |
| ------------------- | --------------------------------------------------------------------------- |
| `component`, `id`   | The component's kind and instance ID.                                       |
| `label`, `row`      | Its accessible name and nearby row text, when available.                    |
| `key`               | The state key that changed, such as `value` or `checked`.                   |
| `value`, `previous` | The new value and the value before it.                                      |
| `reason`            | Why it changed, such as `item-press` or `drag`.                             |
| `source`            | `keyboard`, `mouse`, `touch`, `pen`, `virtual`, `programmatic`, or `agent`. |
| `ref`               | A reference you can pass to `elementOf` or use with the agent bridge.       |

Changes canceled with `details.cancel()` aren't reported. A controlled component reports the requested change before its owner passes the value back; the notification doesn't mean the owner has accepted it.

Password, card, and one-time-code fields report `"[redacted]"` for both `value` and `previous`.

## Watch Part of a Page

Use `within` to limit the stream to a panel or another element:

```ts
import { observe } from "@ui-sdk/core/observe"

const stop = observe((change) => console.log(change), {
  within: () => document.querySelector("#settings"),
})
```

The function can return an element that mounts later. You can also pass an element directly. A component is included when any of its parts is inside it, so a popup can belong to the panel through its trigger.

## Wait for an Interaction to Finish

Some components change a value repeatedly before committing it. By default, a slider drag produces one notification when it finishes, with `previous` taken from before the drag.

Use `live: true` for every intermediate change instead:

```ts
import { observe } from "@ui-sdk/core/observe"

const stop = observe((change) => console.log(change.value), { live: true })
```

The live stream leaves out commit notifications. State that doesn't commit, such as a text field's input, reports each change in either mode. Changes made by code or an agent are reported immediately.

## Keep the Source After an Await

An agent's press reports `"agent"`, and so do the changes it causes while the press runs. Work that finishes later, like a save that awaits a request, runs after the press is over, so its changes report `"programmatic"`.

Every action that can return a Promise, like a Button's click handler, a Close part's `action` or a toast's buttons, receives the press's `source` after the event. Pass it to `withSource` around the changes made after the `await`:

```ts
import type { ButtonActionDetails } from "@ui-sdk/core/button"
import { withSource } from "@ui-sdk/core/observe"

async function onSave(event: MouseEvent, { source }: ButtonActionDetails) {
  await save()
  withSource(source, () => status.setValue("Saved"))
}
```

Here `status` is an Input's controller. When an agent presses Save, the new status reports `"agent"`, as it would have during the press. A change someone makes while the save runs keeps its own source, since only the function passed to `withSource` takes the press's source. Each framework's Button page shows the handler in place.

## Find the Element

`elementOf` finds the root element, or the first available part, for a component's ref. For example, scroll an agent's changed control into view:

```ts
import { elementOf, observe } from "@ui-sdk/core/observe"

const stop = observe((change) => {
  if (change.source === "agent") {
    elementOf(change.ref)?.scrollIntoView({ block: "nearest" })
  }
})
```

To keep an agent informed between turns, see [Observe Changes](/docs/agents/connect-an-agent/observe-changes). To record changes you can undo, see [History and Undo](/docs/agents/connect-an-agent/history-and-undo).
