Dollie Editor SDK guide

Show and Save the Edit Trail

Let people review manual and Copilot edits, undo a specific AI change, and return to an earlier saved revision

The edit trail answers three simple questions:

  • What changed?
  • Was the change made by a person or by the Copilot?
  • Can I undo it?

The Editor already records these details for every page change. You choose which parts to show and whether to keep them after the page is saved.

What people see

The built-in timeline appears in the Editor toolbar. It shows:

  • manual edits;
  • Copilot and AI fill edits;
  • edits that can still be redone;
  • saved revisions from earlier sessions.

Clicking an edit from the current session moves the page to that point in its undo history. Clicking a saved revision calls your restore handler.

Copilot edits can also appear below the chat message that produced them. These markers offer Undo, Redo, or Revert, depending on the page's current state. A section that was removed is shown as unavailable instead of offering an action that cannot work.

Add saved revisions to the timeline

PageBuilderEditor already includes the timeline button. Pass the saved revisions returned by your page store:

<PageBuilderEditor
    editor={editor}
    revisions={revisions}
    onRestoreRevision={async (revisionId) => {
        if (!transport.restoreRevision) return;

        await transport.restoreRevision(pageId, revisionId);
        window.location.reload();
    }}
/>

Restoring a revision should create a new current revision. It should not delete or rewrite the old history.

Show Copilot changes in chat

useEditorCopilot returns a trail handle. Pass it directly to AgentChat:

const { trail } = useEditorCopilot({
    editor,
    fill,
    catalog,
    sections,
});

<AgentChat markers={trail} />

The chat kit places each marker below the assistant message that produced the edit. This works when the apply result includes its message id; the built-in Editor result renderers do that automatically.

Save the trail with the page

The live undo stack stays in memory. Do not save every keystroke as a revision.

When the user saves, send trail.pendingTrail with the page definition. Call trail.markSaved() only after the server confirms the save:

const saved = await transport.save(pageId, {
    definition: serializeEditorState(editor.state),
    revision: document.revision,
    trail: trail.pendingTrail,
});

trail.markSaved();

Each revision summary may contain:

type PageRevisionTrailEntry = {
    id: string;
    at: number;
    source: 'user' | 'copilot' | 'fill';
    label: string;
    targetKey?: string;
    afterMessageId?: string;
    snapshot?: PageRevisionTrailSnapshot;
};

Manual entries contain a label and other small pieces of metadata. Copilot and fill entries also contain the before-and-after snapshot needed to revert that specific change.

Section snapshots also keep the section's position at apply time. The Editor uses the saved content and position to reconnect the marker to the current session's section id after a reload.

Your page store must return the saved trail from listRevisions. The built-in file store already does this.

Listen for edits in your own interface

Use subscribe when your product needs a notification, audit panel, or another custom surface:

useEffect(() => {
    return trail.subscribe((event) => {
        if (event.type === 'recorded') {
            analytics.track('page edit recorded', {
                label: event.entry.meta.label,
                source: event.entry.meta.source,
            });
        }
    });
}, [trail]);

A recorded event has this shape:

type EditorTrailEvent = {
    type: 'recorded';
    entry: {
        meta: {
            id: string;
            at: number;
            source: 'user' | 'copilot' | 'fill';
            label: string;
            targetKey?: string;
            afterMessageId?: string;
        };
        position: number;
        branch: 'applied' | 'redo';
        current: boolean;
    };
};

subscribe reports new edit entries. It does not replace your page store and it does not send anything to the Copilot.

Build another trail interface

useEditorTrail(editor) exposes the same state used by the built-in UI:

Value Use
entries Current session edits, including the redo branch
markers Copilot and fill edits with live revert actions
currentPosition Current point in the session history
jumpTo(position) Move through the undo or redo stack
pendingTrail Entries that have not been included in a successful save
markSaved() Move the save checkpoint forward
subscribe(listener) Hear about newly recorded edits

Use Marker, MarkerIcon, and MarkerContent to match the built-in visual language. Marker supports default, border, and separator variants and accepts a Base UI render prop when it needs to behave as a button or link.

The registry descriptions for these building blocks are:

  • Marker: A small status or activity row for edits, saves, and other events.
  • Edit timeline: A toolbar history panel that combines live page edits with saved revision checkpoints.

Keep markers out of the model prompt

Markers are interface history, not instructions to the model.

Do not turn them into synthetic chat messages. They can become stale after an undo, a later manual edit, or a revision restore.

The latest page_context remains the Copilot's source of truth for the page. Send that current context with every user message.