From 3d6b9af314936edddab0a019d9aa161ba6b510f5 Mon Sep 17 00:00:00 2001 From: Maxim IJzelendoorn Date: Sat, 21 Mar 2026 20:07:24 +0100 Subject: [PATCH 1/2] feat(web): restore user prompt to composer input on revert When reverting a checkpoint, the original user prompt text is now restored into the composer input field so the user can edit and re-send without retyping. Uses deriveDisplayedUserMessageState to extract just the visible prompt text (stripping terminal context blocks). Co-Authored-By: Claude Opus 4.6 --- apps/web/src/components/ChatView.tsx | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/apps/web/src/components/ChatView.tsx b/apps/web/src/components/ChatView.tsx index e628f6ea6..9e7ef65b9 100644 --- a/apps/web/src/components/ChatView.tsx +++ b/apps/web/src/components/ChatView.tsx @@ -135,6 +135,7 @@ import { } from "../composerDraftStore"; import { appendTerminalContextsToPrompt, + deriveDisplayedUserMessageState, formatTerminalContextLabel, insertInlineTerminalContextPlaceholder, removeInlineTerminalContextPlaceholder, @@ -2302,7 +2303,7 @@ export default function ChatView({ threadId }: ChatViewProps) { }; const onRevertToTurnCount = useCallback( - async (turnCount: number) => { + async (turnCount: number, promptToRestore?: string) => { const api = readNativeApi(); if (!api || !activeThread || isRevertingCheckpoint) return; @@ -2338,8 +2339,20 @@ export default function ChatView({ threadId }: ChatViewProps) { ); } setIsRevertingCheckpoint(false); + + // Restore the reverted user prompt into the composer after state settles. + // We use setTimeout to defer past the server state sync (throttled at ~100ms) + // so the restored prompt is not overwritten by re-renders from the sync. + if (promptToRestore) { + setTimeout(() => { + promptRef.current = promptToRestore; + setPrompt(promptToRestore); + setComposerCursor(collapseExpandedComposerCursor(promptToRestore, promptToRestore.length)); + scheduleComposerFocus(); + }, 300); + } }, - [activeThread, isConnecting, isRevertingCheckpoint, isSendBusy, phase, setThreadError], + [activeThread, isConnecting, isRevertingCheckpoint, isSendBusy, phase, scheduleComposerFocus, setPrompt, setThreadError], ); const onSend = async (e?: { preventDefault: () => void }) => { @@ -3431,7 +3444,14 @@ export default function ChatView({ threadId }: ChatViewProps) { if (typeof targetTurnCount !== "number") { return; } - void onRevertToTurnCount(targetTurnCount); + // Find the user message and extract just the visible prompt text (stripping + // terminal context blocks and other metadata) so we can restore it into the + // composer after reverting. + const userMessage = timelineMessages.find((m) => m.id === messageId && m.role === "user"); + const promptText = userMessage + ? deriveDisplayedUserMessageState(userMessage.text).visibleText + : undefined; + void onRevertToTurnCount(targetTurnCount, promptText); }; // Empty state: no active thread From 8399fcf54c83db0909e5715adda1422c7b3966a5 Mon Sep 17 00:00:00 2001 From: Maxim IJzelendoorn Date: Sat, 21 Mar 2026 20:24:52 +0100 Subject: [PATCH 2/2] fix: only restore prompt to composer on successful revert Skip restoring the user prompt into the composer when the checkpoint revert fails, so users don't see their prompt return alongside an error message. Co-Authored-By: Claude Opus 4.6 --- apps/web/src/components/ChatView.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/web/src/components/ChatView.tsx b/apps/web/src/components/ChatView.tsx index 9e7ef65b9..e1b26c7c2 100644 --- a/apps/web/src/components/ChatView.tsx +++ b/apps/web/src/components/ChatView.tsx @@ -2324,6 +2324,7 @@ export default function ChatView({ threadId }: ChatViewProps) { setIsRevertingCheckpoint(true); setThreadError(activeThread.id, null); + let reverted = false; try { await api.orchestration.dispatchCommand({ type: "thread.checkpoint.revert", @@ -2332,6 +2333,7 @@ export default function ChatView({ threadId }: ChatViewProps) { turnCount, createdAt: new Date().toISOString(), }); + reverted = true; } catch (err) { setThreadError( activeThread.id, @@ -2340,10 +2342,10 @@ export default function ChatView({ threadId }: ChatViewProps) { } setIsRevertingCheckpoint(false); - // Restore the reverted user prompt into the composer after state settles. + // Restore the reverted user prompt into the composer only on success. // We use setTimeout to defer past the server state sync (throttled at ~100ms) // so the restored prompt is not overwritten by re-renders from the sync. - if (promptToRestore) { + if (reverted && promptToRestore) { setTimeout(() => { promptRef.current = promptToRestore; setPrompt(promptToRestore);