# History and Undo

Record component values, step through changes and try suggestions you can revert.

`createHistory` records changes to component values. `stageChanges` applies a set of values and gives you a way to restore the previous ones. Both use the agent bridge to change the controls.

For undo inside one component, like a drawing or a board, record its value with [`createUndoStack`](/docs/core/kernel/handle-changes#undo) instead.

These helpers are experimental. The examples reuse the bridge from the [Guide](/docs/agents/connect-an-agent/guide), including its permission policy. Import the helpers from your framework's `history` entry or `@ui-sdk/core/history`.

## Start Recording

Start recording before the changes you want to keep:

```ts
import { createHistory } from "@ui-sdk/react/history"
import { agent } from "./agent"

const history = createHistory(agent)
```

By default, the history records `value`, `checked` and `pressed` changes across the page, from people, agents, and your app. Use `within` to limit it to one element, such as the Guide's `<main>`:

```ts
const history = createHistory(agent, {
  within: () => document.querySelector("main"),
  keys: ["value", "checked"],
})
```

`within` accepts an element or a function that returns one. `keys` replaces the list of state keys to record. Restoring a key uses its `set<Key>` action, such as `setValue` or `setChecked`, so choose keys the component can set through the bridge.

Keep password, card and one-time-code fields outside the recorded panel. Their values are redacted, so the history can't restore the originals: their entries carry `redacted: true`, and seeking or undoing leaves those fields as they are and reports them among the changes it couldn't put back.

## Read the History

`history.entries()` returns entries from oldest to newest. Each has an `id`, component `ref`, `label`, state `key`, `value`, `previous`, `source` and `reason`.

Use `describeEntry` when you want a short sentence for each entry:

```ts
import { describeEntry } from "@ui-sdk/react/history"

const labels = history.entries().map(describeEntry)
```

For example: "Agent set Project name to Atlas". `history.position()` tells you how far you are through the entries. It starts at `0`; the end is `history.entries().length`.

`history.subscribe(listener)` calls a listener when the entries or position change. Read them inside the listener to update your history UI. It returns a function that unsubscribes.

## Step Back and Forward

Pass an entry count to `seek`. These calls return to the start of the recorded history, then apply it again:

```ts
await history.seek(0)
await history.seek(history.entries().length)
```

`seek` clamps the position to the available history. If someone makes a new recorded change after seeking back, the history drops the entries after that position, like undo in an editor.

The history restores recorded component values. It doesn't undo a network request, recreate a removed component or reverse an action such as saving a form. Restoration goes through `agent.act`, one control at a time, so current permissions, disabled state and change callbacks still apply, and a control that's refused or has left the page doesn't stop the others.

`seek` resolves with the changes it couldn't put back, each as `{ ref, key, value }`, and with an empty array when everything went back. If nothing went back, the position stays where it was.

The history doesn't record its own restoring, but it records every other change made meanwhile. When someone changes a control the history hasn't restored yet, their change stays, and the control is in the result.

## Undo the Agent's Changes

Use `undoSource` to remove the effect of one source from the applied history:

```ts
await history.undoSource("agent")
```

It restores each affected key to its latest value from the other sources, or to its starting value if there isn't one. For example, if a person changes the project name after the agent does, that person's value stays.

The entries it put back remain in the history with `reverted: true`, and seeking through the history continues to skip them. Like `seek`, `undoSource` resolves with the changes it couldn't put back, and their entries stay applied. The same method accepts other source names, such as `"keyboard"` or `"programmatic"`.

A Button whose handler awaits needs one more step. A change the handler makes after the `await` happens once the press is over, so it's recorded as `"programmatic"` and `undoSource("agent")` leaves it. Wrap it in `withSource` with the source the handler receives:

```tsx
import { Button } from "@ui-sdk/react/button"
import { withSource } from "@ui-sdk/react/observe"

function SaveButton() {
  return (
    <Button
      onClick={async (event, { source }) => {
        await save()
        withSource(source, () => status.setValue("Saved"))
      }}
    >
      Save
    </Button>
  )
}
```

Here `status` is an Input's controller. When the agent presses Save, the new status is recorded as the agent's. See [Observe](/docs/core/observe#keep-the-source-after-an-await) for how sources work.

## Keep Interactions Together

A drag that commits arrives as one entry. The history also combines consecutive changes with the same component, key, source and reason, except for `item-press` and `imperative` changes.

To record every intermediate step instead, turn coalescing off:

```ts
const history = createHistory(agent, { coalesce: false })
```

Call `history.clear()` to forget every entry and keep the controls as they are. Call `history.destroy()` when you're done to stop recording and remove its listeners.

## Try a Suggestion

`stageChanges` puts suggested values on the real controls. Try a different region in the Guide's Select:

```ts
import { stageChanges } from "@ui-sdk/react/history"

const staged = await stageChanges(agent, [
  { ref: "Region", value: "north-america" },
])
```

Each change is `{ ref, key?, value }`. `key` defaults to `"value"`; use `"checked"` for a checkbox. The component must be mounted, expose that key in its snapshot, and advertise the matching `set<Key>` action.

Staging accepts a ref or an exact label. It uses the first component with a matching label, so use a ref from `snapshot()` when labels repeat.

Missing components, unavailable setters and refused actions are skipped, and the remaining suggestions are still attempted. `staged.applied` lists successful calls as `{ ref, key, before, value }`. As with `act`, a successful call doesn't prove a controlled owner accepted the value or that a change callback didn't cancel it; read the component state when you need to confirm.

If the person rejects the suggestion, restore the captured values:

```ts
await staged.revert()
```

`revert` runs once, in reverse order, through the same action policy and callbacks. It attempts to restore the captured values even if the person has edited those controls since the preview, and resolves with the entries of `staged.applied` it couldn't put back. A filled password, card or one-time-code field's `before` reads `"[redacted]"`, so `revert` leaves that field as staged and includes it in the result. If the person accepts the suggestion, leave the values in place.

To point at a suggested control, use `elementOf(ref)` from [Observe](/docs/core/observe) with one of the refs in `staged.applied`.
