Restore dialog

Pro

Restore a checkpoint, honestly. The person chooses code, conversation or both (nothing is chosen for them, and Restore stays locked until they do), and the dialog says what a restore doesn't undo: the effects of shell commands the agent ran (listing them when the app knows), changes made outside the agent, and effects that left the workspace such as sent messages and deployments. onRestore restores and then checks; the dialog shows that check with RestoreResult, which never says "Restored" without a check that matched.

Markdown

Preview

Install

With the Candor CLI

First time? Get started sets up the registry and theme. Or with shadcn directly: npx shadcn@latest add @candor/restore-dialog

Brings in: button, checkpoint-list, dialog, locale, locked, radio-group, relative-time, status-dot, utils · npm: lucide-react@^1.67.0

Usage

TSX · The example above
"use client"

import { Button } from "@/components/ui/button"
import { RestoreDialog, RestoreResult, type RestoreOutcome, type RestoreTarget } from "@/components/restore-dialog"

const checkpoint = { label: "Added tests for burst traffic", createdAt: new Date(Date.now() - 9 * 60_000), captured: "both" as const }

// The app restores, then compares each file with the checkpoint's snapshot and reports what it found.
async function restore(target: RestoreTarget): Promise<RestoreOutcome> {
  await new Promise((r) => setTimeout(r, 1200))
  return {
    target,
    checkedAt: new Date(),
    checks: [
      { id: "1", kind: "file", label: "src/server/limiter.ts", status: "matches" },
      { id: "2", kind: "file", label: "src/server/routes.ts", status: "differs", detail: "A formatter on save re-wrapped 3 lines after the restore." },
      { id: "3", kind: "file", label: "package-lock.json", status: "not-checked" },
      ...(target !== "code" ? [{ id: "4", label: "Conversation", area: "conversation" as const, status: "matches" as const }] : []),
    ],
  }
}

const earlier: RestoreOutcome = {
  target: "code",
  checks: [
    { id: "a", kind: "file", label: "src/server/limiter.ts", status: "matches" },
    { id: "b", kind: "file", label: "src/server/limiter.test.ts", status: "matches" },
  ],
}

export default function Example() {
  return (
    <div className="flex max-w-xl flex-col items-start gap-4">
      <RestoreDialog
        checkpoint={checkpoint}
        onRestore={restore}
        commandsRun={["npm install ioredis@5", "npm run db:migrate", "rm -rf .cache/limiter"]}
        outsideChanges={["Priya Shah edited src/config/limits.ts"]}
        externalEffects={["Pushed branch rate-limiter to origin", "Deployed a preview build"]}
        trigger={<Button variant="outline">Restore this checkpoint…</Button>}
      />
      <RestoreResult className="w-full" result={earlier} />
    </div>
  )
}

<RestoreDialog>

PropTypeDefaultDescription
checkpointrequiredPick<Checkpoint, "label" | "createdAt" | "captured">—The checkpoint to restore. What it captured decides which choices are available.
onRestorerequired(target: RestoreTarget) => Promise<RestoreOutcome>—Restore what the person chose, then check the result against the checkpoint and report it. A rejection is shown as an error.
commandsRunstring[] | undefined—Shell commands the agent ran after this checkpoint, as text. Left out, the dialog says it isn't recorded; [] says none ran.
outsideChangesstring[] | undefined—Changes made outside the agent since the checkpoint, when the app recorded them ("Ana edited README.md").
externalEffectsstring[] | undefined—Effects that left the workspace since the checkpoint, when the app recorded them ("Deployed to preview").
lockedPartial<Record<RestoreTarget, string | undefined>> | undefined—Choices the app doesn't allow here, with why ("Only the session owner can rewind the conversation.").
openboolean | undefined—Whether the dialog is open, when the app controls it.
onOpenChange((open: boolean) => void) | undefined—Called when the dialog opens or closes.
triggerReact.ReactElement | undefined—The control that opens the dialog, when the app doesn't control `open`.

<RestoreResult>

What a restore actually did, as the app checked it. "Restored" shows only when every check the app reports matched the checkpoint. Items that weren't checked are shown as not checked, a mismatch is flagged with what differs, and a restore that didn't finish says what happened. Used inside RestoreDialog, or alone after a restore started elsewhere.

PropTypeDefaultDescription
resultrequiredRestoreOutcome—The app's report: what it checked after restoring, and any error.
classNamestring | undefined—Extra classes for the outer element.

Strings

The fixed words, in English by default. Change any of them under restoreDialog in <LocaleProvider strings={{ … }}>; the table is exported as restoreDialogStrings.

KeyEnglish
titleRestore to “{label}”?
savedCheckpoint from {time}.
chooseWhat to restore
bothCode and conversation
bothHintPut the files back and rewind the chat to this point.
codeCode only
codeHintPut the files back. The chat stays as it is.
conversationConversation only
conversationHintRewind the chat to this point. The files stay as they are now.
noFilesThis checkpoint didn’t capture files.
noConversationThis checkpoint didn’t capture the conversation.
notUndoneWhat a restore doesn’t undo
shellShell commands the agent ran
shellDefaultTheir effects stay: installed packages, generated or deleted files, database and service changes.
shellListed{ one: "{count} command ran after this checkpoint:", other: "{count} commands ran after this checkpoint:" }
shellNoneNo commands ran after this checkpoint.
shellUnknownWhich commands ran since then isn’t recorded.
outsideChanges made outside the agent
outsideDefaultEdits by people or other tools since this checkpoint aren’t tracked. A code restore may overwrite them where they touch the same files.
externalEffects outside this workspace
externalDefaultSent messages, deployments, pushed commits and calls to other services can’t be taken back.
recordedRecorded since this checkpoint:
cancelCancel
restoreRestore
chooseFirstChoose what to restore first.
restoringRestoring and checking…
closeClose
tryAgainTry again
noReasonNo reason was given.
resultRestore result
matchedRestored
matchedBody{ one: "Checked: {count} item matches the checkpoint.", other: "Checked: all {count} items match the checkpoint." }
differsDoesn’t match the checkpoint
differsBody{ one: "{count} item differs from the checkpoint after the restore.", other: "{count} items differ from the checkpoint after the restore." }
partialPartly checked
partialBody{ one: "{matched} matched. {count} item wasn’t checked, so whether it was put back isn’t known.", other: "{matched} matched. {count} items weren’t checked, so whether they were put back isn’t known." }
areaNotCheckedNothing checked {area}, so whether it was put back isn’t known.
codeAreathe code
conversationAreathe conversation
uncheckedRestore ran, but wasn’t checked
uncheckedBodyNothing was checked afterwards, so whether it matches the checkpoint isn’t known.
failedThe restore didn’t finish
failedBodySome items may have changed before it stopped.
statusMatchesMatches
statusDiffersDiffers
statusNotCheckedNot checked
noDetailThe app didn’t say what differs.
checkedAtChecked {time}

Types

RestoreTarget
What a restore puts back: the code (files), the conversation, or both.
"both" | "code" | "conversation"
RestoreCheckStatus
What the app found when it checked one file or area after the restore.
"matches" | "differs" | "not-checked"
RestoreCheck
One file or area the app checked (or didn't) against the checkpoint after restoring.
{ id: string /** The file path ("src/server/limiter.ts") or area ("Conversation", "Lockfile"). */ label: string /** "file" shows the label as a path. Defaults to "area". */ kind?: "file" | "area" | undefined /** * Which part of the restore this check covers. A file is code unless you say otherwise. "Restored" needs a check * for every part restored: with "both", one covering the code and one cover
RestoreOutcome
What the app reports after a restore: each check it made, and an error if the restore didn't finish.
{ /** What was restored. */ target: RestoreTarget /** What the app checked afterwards. Empty means nothing was checked, and the result says so. */ checks: RestoreCheck[] /** Set when the restore didn't finish: what happened, in plain words. */ error?: string | undefined /** When the checks were made. */ checkedAt?: Date | string | undefined }