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:
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:
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:
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:
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:
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:
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. To record changes you can undo, see History and Undo.