feat: add initial checkpoint as state-driven UI element

Show the initial checkpoint (workspace state at task start) as a fixed UI element
at the top of the chat, with visual states for pending/ready/failed.

This replaces the previous approach of a header button and provides better UX:
- Pending state: greyed out with spinner while initializing
- Ready state: full color with restore/diff menu options
- Failed state: warning styling with error indication

Changes:
- Add initialCheckpointState and initialCheckpointHash to extension state
- New InitialCheckpoint component with three visual states
- CheckpointMenu now supports isInitial prop to hide irrelevant options
- Backend emits state transitions instead of messages
This commit is contained in:
Hannes Rudolph 2026-01-29 12:28:47 -07:00
parent 0c53f1937a
commit 8a433c6e1f
11 changed files with 523 additions and 17 deletions

View file

@ -60,6 +60,7 @@ export interface ExtensionMessage {
| "deleteCustomModeCheck"
| "currentCheckpointUpdated"
| "checkpointInitWarning"
| "initialCheckpointState"
| "browserToolEnabled"
| "browserConnectionResult"
| "remoteBrowserEnabled"
@ -116,6 +117,9 @@ export interface ExtensionMessage {
type: "WAIT_TIMEOUT" | "INIT_TIMEOUT"
timeout: number
}
// Initial checkpoint state for state-driven UI
initialCheckpointState?: "pending" | "ready" | "failed" | null
initialCheckpointHash?: string | null
action?:
| "chatButtonClicked"
| "settingsButtonClicked"
@ -408,6 +412,10 @@ export type ExtensionState = Pick<
featureRoomoteControlEnabled: boolean
openAiCodexIsAuthenticated?: boolean
debug?: boolean
// Initial checkpoint state for state-driven UI
initialCheckpointState?: "pending" | "ready" | "failed" | null
initialCheckpointHash?: string | null
}
export interface Command {

View file

@ -25,6 +25,14 @@ function sendCheckpointInitWarn(task: Task, type?: "WAIT_TIMEOUT" | "INIT_TIMEOU
})
}
function sendInitialCheckpointState(task: Task, state: "pending" | "ready" | "failed" | null, hash?: string) {
task.providerRef.deref()?.postMessageToWebview({
type: "initialCheckpointState",
initialCheckpointState: state,
initialCheckpointHash: hash ?? null,
})
}
export async function getCheckpointService(task: Task, { interval = 250 }: { interval?: number } = {}) {
if (!task.enableCheckpoints) {
return undefined
@ -98,6 +106,7 @@ export async function getCheckpointService(task: Task, { interval = 250 }: { int
)
if (!task?.checkpointService) {
sendCheckpointInitWarn(task, "INIT_TIMEOUT", task.checkpointTimeout)
sendInitialCheckpointState(task, "failed")
task.enableCheckpoints = false
return undefined
} else {
@ -121,6 +130,7 @@ export async function getCheckpointService(task: Task, { interval = 250 }: { int
} catch (err) {
if (err.name === "TimeoutError" && task.enableCheckpoints) {
sendCheckpointInitWarn(task, "INIT_TIMEOUT", task.checkpointTimeout)
sendInitialCheckpointState(task, "failed")
}
log(`[Task#getCheckpointService] ${err.message}`)
task.enableCheckpoints = false
@ -140,6 +150,7 @@ async function checkGitInstallation(
if (!gitInstalled) {
log("[Task#getCheckpointService] Git is not installed, disabling checkpoints")
sendInitialCheckpointState(task, "failed")
task.enableCheckpoints = false
task.checkpointServiceInitializing = false
@ -157,9 +168,18 @@ async function checkGitInstallation(
}
// Git is installed, proceed with initialization
service.on("initialize", () => {
service.on("initialize", ({ baseHash }) => {
log("[Task#getCheckpointService] service initialized")
task.checkpointServiceInitializing = false
// Send initial checkpoint state as ready with the hash
sendInitialCheckpointState(task, "ready", baseHash)
// Update webview with initial checkpoint hash (for currentCheckpoint tracking)
provider?.postMessageToWebview({
type: "currentCheckpointUpdated",
text: baseHash,
})
})
service.on("checkpoint", ({ fromHash: from, toHash: to, suppressMessage }) => {
@ -195,15 +215,20 @@ async function checkGitInstallation(
log("[Task#getCheckpointService] initializing shadow git")
// Set pending state before starting initialization
sendInitialCheckpointState(task, "pending")
try {
await service.initShadowGit()
} catch (err) {
log(`[Task#getCheckpointService] initShadowGit -> ${err.message}`)
sendInitialCheckpointState(task, "failed")
task.enableCheckpoints = false
}
} catch (err) {
log(`[Task#getCheckpointService] Unexpected error during Git check: ${err.message}`)
console.error("Git check error:", err)
sendInitialCheckpointState(task, "failed")
task.enableCheckpoints = false
task.checkpointServiceInitializing = false
}

View file

@ -44,6 +44,7 @@ import TaskHeader from "./TaskHeader"
import SystemPromptWarning from "./SystemPromptWarning"
import ProfileViolationWarning from "./ProfileViolationWarning"
import { CheckpointWarning } from "./CheckpointWarning"
import { InitialCheckpoint } from "./checkpoints/InitialCheckpoint"
import { QueuedMessages } from "./QueuedMessages"
import { WorktreeSelector } from "./WorktreeSelector"
import DismissibleUpsell from "../common/DismissibleUpsell"
@ -97,6 +98,9 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
messageQueue = [],
isBrowserSessionActive,
showWorktreesInHomeScreen,
initialCheckpointState,
initialCheckpointHash,
enableCheckpoints,
} = useExtensionState()
const messagesRef = useRef(messages)
@ -1510,6 +1514,13 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
<CheckpointWarning warning={checkpointWarning} />
</div>
)}
{/* Initial Checkpoint UI - state-driven element */}
{initialCheckpointState && enableCheckpoints && (
<div className="px-3">
<InitialCheckpoint state={initialCheckpointState} hash={initialCheckpointHash} />
</div>
)}
</>
) : (
<div className="flex flex-col h-full justify-center p-6 min-h-0 overflow-y-auto gap-4 relative">

View file

@ -12,6 +12,7 @@ type CheckpointMenuBaseProps = {
ts: number
commitHash: string
checkpoint: Checkpoint
isInitial?: boolean
}
type CheckpointMenuControlledProps = {
onOpenChange: (open: boolean) => void
@ -21,7 +22,7 @@ type CheckpointMenuUncontrolledProps = {
}
type CheckpointMenuProps = CheckpointMenuBaseProps & (CheckpointMenuControlledProps | CheckpointMenuUncontrolledProps)
export const CheckpointMenu = ({ ts, commitHash, checkpoint, onOpenChange }: CheckpointMenuProps) => {
export const CheckpointMenu = ({ ts, commitHash, checkpoint, isInitial, onOpenChange }: CheckpointMenuProps) => {
const { t } = useTranslation()
const [internalRestoreOpen, setInternalRestoreOpen] = useState(false)
const [restoreConfirming, setRestoreConfirming] = useState(false)
@ -95,11 +96,14 @@ export const CheckpointMenu = ({ ts, commitHash, checkpoint, onOpenChange }: Che
return (
<div className="flex flex-row gap-1">
<StandardTooltip content={t("chat:checkpoint.menu.viewDiff")}>
<Button variant="ghost" size="icon" onClick={onCheckpointDiff}>
<span className="codicon codicon-diff-single" />
</Button>
</StandardTooltip>
{/* Hide "View Diff" for initial checkpoint - no previous checkpoint to diff against */}
{!isInitial && (
<StandardTooltip content={t("chat:checkpoint.menu.viewDiff")}>
<Button variant="ghost" size="icon" onClick={onCheckpointDiff}>
<span className="codicon codicon-diff-single" />
</Button>
</StandardTooltip>
)}
<Popover
open={restoreOpen}
onOpenChange={(open) => {
@ -175,15 +179,18 @@ export const CheckpointMenu = ({ ts, commitHash, checkpoint, onOpenChange }: Che
</StandardTooltip>
<PopoverContent align="end" container={portalContainer} className="w-auto min-w-max">
<div className="flex flex-col gap-2">
<Button
variant="secondary"
onClick={() => {
onDiffFromInit()
setMoreOpen(false)
}}>
<span className="codicon codicon-versions mr-2" />
{t("chat:checkpoint.menu.viewDiffFromInit")}
</Button>
{/* Hide "View All Changes" for initial checkpoint - already at init */}
{!isInitial && (
<Button
variant="secondary"
onClick={() => {
onDiffFromInit()
setMoreOpen(false)
}}>
<span className="codicon codicon-versions mr-2" />
{t("chat:checkpoint.menu.viewDiffFromInit")}
</Button>
)}
<Button
variant="secondary"
onClick={() => {

View file

@ -83,7 +83,9 @@ export const CheckpointSaved = ({ checkpoint, currentHash, ...props }: Checkpoin
onMouseLeave={handleMouseLeave}>
<div className="flex items-center gap-2 text-blue-400 whitespace-nowrap">
<GitCommitVertical className="w-4" />
<span className="font-semibold">{t("chat:checkpoint.regular")}</span>
<span className="font-semibold">
{metadata.isInitial ? t("chat:checkpoint.initial") : t("chat:checkpoint.regular")}
</span>
{isCurrent && <span className="text-muted">({t("chat:checkpoint.current")})</span>}
</div>
<span
@ -99,6 +101,7 @@ export const CheckpointSaved = ({ checkpoint, currentHash, ...props }: Checkpoin
ts={props.ts}
commitHash={props.commitHash}
checkpoint={metadata}
isInitial={metadata.isInitial ?? false}
onOpenChange={handlePopoverOpenChange}
/>
</div>

View file

@ -0,0 +1,131 @@
import { useMemo, useRef, useState, useEffect, useCallback } from "react"
import { useTranslation } from "react-i18next"
import { cn } from "@/lib/utils"
import { CheckpointMenu } from "./CheckpointMenu"
import { GitCommitVertical, Loader2, AlertTriangle } from "lucide-react"
import { StandardTooltip } from "@/components/ui"
type InitialCheckpointProps = {
state: "pending" | "ready" | "failed"
hash?: string | null
}
export const InitialCheckpoint = ({ state, hash }: InitialCheckpointProps) => {
const { t } = useTranslation()
const [isPopoverOpen, setIsPopoverOpen] = useState(false)
const [isClosing, setIsClosing] = useState(false)
const [isHovering, setIsHovering] = useState(false)
const closeTimer = useRef<number | null>(null)
useEffect(() => {
return () => {
if (closeTimer.current) {
window.clearTimeout(closeTimer.current)
closeTimer.current = null
}
}
}, [])
const handlePopoverOpenChange = useCallback((open: boolean) => {
setIsPopoverOpen(open)
if (open) {
setIsClosing(false)
if (closeTimer.current) {
window.clearTimeout(closeTimer.current)
closeTimer.current = null
}
} else {
setIsClosing(true)
closeTimer.current = window.setTimeout(() => {
setIsClosing(false)
closeTimer.current = null
}, 200) // keep menu visible briefly to avoid popover jump
}
}, [])
const handleMouseEnter = useCallback(() => {
setIsHovering(true)
}, [])
const handleMouseLeave = useCallback(() => {
setIsHovering(false)
}, [])
// Menu is visible when hovering, popover is open, or briefly after popover closes
// But only when the state is 'ready'
const menuVisible = state === "ready" && (isHovering || isPopoverOpen || isClosing)
// Create checkpoint metadata for the menu
const checkpointMetadata = useMemo(() => {
if (!hash) {
return undefined
}
return {
from: hash,
to: hash,
isInitial: true,
}
}, [hash])
const isPending = state === "pending"
const isReady = state === "ready"
const isFailed = state === "failed"
return (
<div
className={cn(
"flex items-center justify-between gap-2 pt-2 pb-3",
isPending && "opacity-50",
isFailed && "opacity-75",
)}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
data-testid="initial-checkpoint">
<div
className={cn(
"flex items-center gap-2 whitespace-nowrap",
isReady && "text-blue-400",
isPending && "text-muted",
isFailed && "text-destructive",
)}>
{isPending && <Loader2 className="w-4 animate-spin" data-testid="initial-checkpoint-spinner" />}
{isReady && <GitCommitVertical className="w-4" />}
{isFailed && <AlertTriangle className="w-4" />}
<span className="font-semibold">
{isPending && t("chat:checkpoint.initializing")}
{isReady && t("chat:checkpoint.initial")}
{isFailed && (
<StandardTooltip content={t("chat:checkpoint.failedDescription")}>
<span>{t("chat:checkpoint.failed")}</span>
</StandardTooltip>
)}
</span>
</div>
<span
className={cn("block w-full h-[2px] mt-[2px] text-xs")}
style={{
backgroundImage: isPending
? "linear-gradient(90deg, rgba(128, 128, 128, .4), rgba(128, 128, 128, .4) 80%, rgba(128, 128, 128, 0) 99%)"
: isFailed
? "linear-gradient(90deg, rgba(239, 68, 68, .4), rgba(239, 68, 68, .4) 80%, rgba(239, 68, 68, 0) 99%)"
: "linear-gradient(90deg, rgba(0, 188, 255, .65), rgba(0, 188, 255, .65) 80%, rgba(0, 188, 255, 0) 99%)",
}}></span>
{/* Only show menu when ready and hash is available */}
{isReady && hash && checkpointMetadata && (
<div
data-testid="initial-checkpoint-menu-container"
className={cn("h-4 -mt-2", menuVisible ? "block" : "hidden")}>
<CheckpointMenu
ts={0} // Initial checkpoint doesn't have a ts
commitHash={hash}
checkpoint={checkpointMetadata}
isInitial={true}
onOpenChange={handlePopoverOpenChange}
/>
</div>
)}
</div>
)
}

View file

@ -186,3 +186,147 @@ describe("CheckpointSaved popover visibility", () => {
})
})
})
describe("CheckpointSaved label rendering", () => {
const baseProps = {
ts: 123,
commitHash: "abc123",
currentHash: "zzz999",
}
it("renders initial checkpoint label when isInitial is true", () => {
const { getByText } = render(
<CheckpointSaved
{...baseProps}
checkpoint={{ from: "abc123", to: "abc123", isInitial: true } as Record<string, unknown>}
/>,
)
// Test uses i18n key since translations may not be loaded in test environment
expect(getByText("chat:checkpoint.initial")).toBeTruthy()
})
it("renders regular checkpoint label when isInitial is false", () => {
const { getByText } = render(
<CheckpointSaved
{...baseProps}
checkpoint={{ from: "prev123", to: "abc123", isInitial: false } as Record<string, unknown>}
/>,
)
expect(getByText("chat:checkpoint.regular")).toBeTruthy()
})
it("renders regular checkpoint label when isInitial is undefined", () => {
const { getByText } = render(
<CheckpointSaved
{...baseProps}
checkpoint={{ from: "prev123", to: "abc123" } as Record<string, unknown>}
/>,
)
expect(getByText("chat:checkpoint.regular")).toBeTruthy()
})
})
describe("CheckpointMenu isInitial behavior", () => {
const baseProps = {
ts: 123,
commitHash: "abc123",
currentHash: "zzz999",
}
it("hides View Diff button when isInitial is true", () => {
const { container } = render(
<CheckpointSaved
{...baseProps}
checkpoint={{ from: "abc123", to: "abc123", isInitial: true } as Record<string, unknown>}
/>,
)
// The View Diff button should not be rendered
const diffButton = container.querySelector('[aria-label="View Diff"]')
expect(diffButton).toBeNull()
})
it("shows View Diff button when isInitial is false", async () => {
const { container } = render(
<CheckpointSaved
{...baseProps}
checkpoint={{ from: "prev123", to: "abc123", isInitial: false } as Record<string, unknown>}
/>,
)
// Hover to make menu visible
const parentDiv = container.querySelector("[class*='flex items-center justify-between']") as HTMLElement
fireEvent.mouseEnter(parentDiv)
// The View Diff button should be rendered (using codicon class as identifier)
await waitFor(() => {
const diffIcon = container.querySelector(".codicon-diff-single")
expect(diffIcon).toBeTruthy()
})
})
it("hides View All Changes button when isInitial is true", async () => {
const { container } = render(
<CheckpointSaved
{...baseProps}
checkpoint={{ from: "abc123", to: "abc123", isInitial: true } as Record<string, unknown>}
/>,
)
// Hover to make menu visible
const parentDiv = container.querySelector("[class*='flex items-center justify-between']") as HTMLElement
fireEvent.mouseEnter(parentDiv)
// Open the "more" popover
await waitForOpenHandler()
// The "View All Changes" button with codicon-versions should not be rendered
const versionsIcon = container.querySelector(".codicon-versions")
expect(versionsIcon).toBeNull()
})
it("shows View Changes Since This Checkpoint regardless of isInitial", async () => {
const { container } = render(
<CheckpointSaved
{...baseProps}
checkpoint={{ from: "abc123", to: "abc123", isInitial: true } as Record<string, unknown>}
/>,
)
// Hover to make menu visible
const parentDiv = container.querySelector("[class*='flex items-center justify-between']") as HTMLElement
fireEvent.mouseEnter(parentDiv)
// The "View Changes Since This Checkpoint" button with codicon-diff should be present
await waitFor(() => {
const diffIcon = container.querySelector(".codicon-diff")
expect(diffIcon).toBeTruthy()
})
})
it("shows restore options regardless of isInitial", async () => {
const { getByTestId, container } = render(
<CheckpointSaved
{...baseProps}
checkpoint={{ from: "abc123", to: "abc123", isInitial: true } as Record<string, unknown>}
/>,
)
// Hover to make menu visible
const parentDiv = container.querySelector("[class*='flex items-center justify-between']") as HTMLElement
fireEvent.mouseEnter(parentDiv)
// Open the restore popover
await waitForOpenHandler()
lastOnOpenChange?.(true)
// Restore buttons should be available
await waitFor(() => {
expect(getByTestId("restore-files-btn")).toBeTruthy()
expect(getByTestId("restore-files-and-task-btn")).toBeTruthy()
})
})
})

View file

@ -0,0 +1,161 @@
// npx vitest run src/components/chat/checkpoints/__tests__/InitialCheckpoint.spec.tsx
// Capture onOpenChange from Popover to control open/close in tests
let _lastOnOpenChange: ((open: boolean) => void) | undefined
vi.mock("@/components/ui", () => {
// Minimal UI primitives to ensure deterministic behavior in tests
return {
Button: ({ children, ...rest }: any) => <button {...rest}>{children}</button>,
StandardTooltip: ({ children }: any) => <>{children}</>,
Popover: (props: any) => {
const { children, onOpenChange, open, ...rest } = props
if (rest["data-testid"] === "restore-popover") {
_lastOnOpenChange = onOpenChange
}
return (
<div data-testid={rest["data-testid"]} data-open={open}>
{children}
</div>
)
},
PopoverTrigger: ({ children }: any) => <div data-testid="popover-trigger">{children}</div>,
PopoverContent: ({ children, className, ...rest }: any) => (
<div data-testid="popover-content" className={className} {...rest}>
{children}
</div>
),
}
})
import { render, screen, fireEvent, waitFor } from "@/utils/test-utils"
import React from "react"
import { InitialCheckpoint } from "../InitialCheckpoint"
describe("InitialCheckpoint", () => {
beforeEach(() => {
_lastOnOpenChange = undefined
})
describe("visual states", () => {
it("renders pending state correctly", () => {
const { getByTestId, getByText } = render(<InitialCheckpoint state="pending" />)
const container = getByTestId("initial-checkpoint")
expect(container).toBeTruthy()
// Should show spinner
expect(getByTestId("initial-checkpoint-spinner")).toBeTruthy()
// Should show initializing text
expect(getByText("chat:checkpoint.initializing")).toBeTruthy()
// Menu should not be visible
expect(screen.queryByTestId("initial-checkpoint-menu-container")).toBeNull()
})
it("renders ready state correctly", () => {
const { getByTestId, getByText } = render(<InitialCheckpoint state="ready" hash="abc123" />)
const container = getByTestId("initial-checkpoint")
expect(container).toBeTruthy()
// Should not show spinner
expect(screen.queryByTestId("initial-checkpoint-spinner")).toBeNull()
// Should show "Initial State" text
expect(getByText("chat:checkpoint.initial")).toBeTruthy()
// Menu container should exist
expect(getByTestId("initial-checkpoint-menu-container")).toBeTruthy()
})
it("renders failed state correctly", () => {
const { getByTestId, getByText } = render(<InitialCheckpoint state="failed" />)
const container = getByTestId("initial-checkpoint")
expect(container).toBeTruthy()
// Should not show spinner
expect(screen.queryByTestId("initial-checkpoint-spinner")).toBeNull()
// Should show failed text
expect(getByText("chat:checkpoint.failed")).toBeTruthy()
// Menu should not be visible
expect(screen.queryByTestId("initial-checkpoint-menu-container")).toBeNull()
})
})
describe("menu visibility in ready state", () => {
it("hides menu by default when not hovering", () => {
const { getByTestId } = render(<InitialCheckpoint state="ready" hash="abc123" />)
const menuContainer = getByTestId("initial-checkpoint-menu-container")
expect(menuContainer.className).toContain("hidden")
})
it("shows menu when hovering", async () => {
const { getByTestId } = render(<InitialCheckpoint state="ready" hash="abc123" />)
const container = getByTestId("initial-checkpoint")
const menuContainer = getByTestId("initial-checkpoint-menu-container")
// Initially hidden
expect(menuContainer.className).toContain("hidden")
// Hover to show menu
fireEvent.mouseEnter(container)
await waitFor(() => {
expect(menuContainer.className).toContain("block")
expect(menuContainer.className).not.toContain("hidden")
})
// Mouse leave to hide menu
fireEvent.mouseLeave(container)
await waitFor(() => {
expect(menuContainer.className).toContain("hidden")
})
})
})
describe("styling", () => {
it("applies opacity styling for pending state", () => {
const { getByTestId } = render(<InitialCheckpoint state="pending" />)
const container = getByTestId("initial-checkpoint")
expect(container.className).toContain("opacity-50")
})
it("applies opacity styling for failed state", () => {
const { getByTestId } = render(<InitialCheckpoint state="failed" />)
const container = getByTestId("initial-checkpoint")
expect(container.className).toContain("opacity-75")
})
it("does not apply opacity for ready state", () => {
const { getByTestId } = render(<InitialCheckpoint state="ready" hash="abc123" />)
const container = getByTestId("initial-checkpoint")
expect(container.className).not.toContain("opacity-50")
expect(container.className).not.toContain("opacity-75")
})
})
describe("menu not rendered without hash in ready state", () => {
it("does not render menu when hash is null in ready state", () => {
render(<InitialCheckpoint state="ready" hash={null} />)
expect(screen.queryByTestId("initial-checkpoint-menu-container")).toBeNull()
})
it("does not render menu when hash is undefined in ready state", () => {
render(<InitialCheckpoint state="ready" />)
expect(screen.queryByTestId("initial-checkpoint-menu-container")).toBeNull()
})
})
})

View file

@ -3,6 +3,7 @@ import { z } from "zod"
export const checkpointSchema = z.object({
from: z.string(),
to: z.string(),
isInitial: z.boolean().optional(),
})
export type Checkpoint = z.infer<typeof checkpointSchema>

View file

@ -39,6 +39,8 @@ export interface ExtensionStateContextType extends ExtensionState {
mcpServers: McpServer[]
hasSystemPromptOverride?: boolean
currentCheckpoint?: string
initialCheckpointState?: "pending" | "ready" | "failed" | null
initialCheckpointHash?: string | null
currentTaskTodos?: TodoItem[] // Initial todos for the current task
filePaths: string[]
openedTabs: Array<{ label: string; isActive: boolean; path?: string }>
@ -283,6 +285,8 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
const [skills, setSkills] = useState<SkillMetadata[]>([])
const [mcpServers, setMcpServers] = useState<McpServer[]>([])
const [currentCheckpoint, setCurrentCheckpoint] = useState<string>()
const [initialCheckpointState, setInitialCheckpointState] = useState<"pending" | "ready" | "failed" | null>(null)
const [initialCheckpointHash, setInitialCheckpointHash] = useState<string | null>(null)
const [extensionRouterModels, setExtensionRouterModels] = useState<RouterModels | undefined>(undefined)
const [marketplaceItems, setMarketplaceItems] = useState<any[]>([])
const [alwaysAllowFollowupQuestions, setAlwaysAllowFollowupQuestions] = useState(false) // Add state for follow-up questions auto-approve
@ -405,6 +409,11 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
setCurrentCheckpoint(message.text)
break
}
case "initialCheckpointState": {
setInitialCheckpointState(message.initialCheckpointState ?? null)
setInitialCheckpointHash(message.initialCheckpointHash ?? null)
break
}
case "listApiConfig": {
setListApiConfigMeta(message.listApiConfig ?? [])
break
@ -492,6 +501,8 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
theme,
mcpServers,
currentCheckpoint,
initialCheckpointState,
initialCheckpointHash,
filePaths,
openedTabs,
commands,

View file

@ -171,6 +171,10 @@
},
"checkpoint": {
"regular": "Checkpoint",
"initial": "Initial State",
"initializing": "Initializing checkpoint...",
"failed": "Checkpoint initialization failed",
"failedDescription": "Unable to create initial checkpoint. Restore features may be unavailable for this task.",
"initializingWarning": "Still initializing checkpoint... If this takes too long, you can disable checkpoints in <settingsLink>settings</settingsLink> and restart your task.",
"menu": {
"viewDiff": "View Diff",