diff --git a/ui/src/components/RoutineHistoryTab.test.tsx b/ui/src/components/RoutineHistoryTab.test.tsx index 66d5264e787..209436cbe16 100644 --- a/ui/src/components/RoutineHistoryTab.test.tsx +++ b/ui/src/components/RoutineHistoryTab.test.tsx @@ -317,4 +317,57 @@ describe("RoutineHistoryTab", () => { ); expect(successCall).toBeTruthy(); }); + + it("invokes onRestored with the restore response so the editor can rehydrate (PAP-3588)", async () => { + const current = createRevision({ id: "revision-2", revisionNumber: 2 }); + const old = createRevision({ + id: "revision-1", + revisionNumber: 1, + snapshot: snapshotV1({ description: "Original description" }), + }); + mockRoutinesApi.listRevisions.mockResolvedValue([current, old]); + const restoredRoutine = createRoutine({ + description: "Original description", + latestRevisionId: "revision-3", + latestRevisionNumber: 3, + }); + mockRoutinesApi.restoreRevision.mockResolvedValue({ + routine: restoredRoutine, + revision: createRevision({ + id: "revision-3", + revisionNumber: 3, + restoredFromRevisionId: "revision-1", + }), + restoredFromRevisionId: "revision-1", + restoredFromRevisionNumber: 1, + secretMaterials: [], + }); + const onRestored = vi.fn(); + await render({ onRestored }); + const oldRow = container.querySelector( + "[data-testid='revision-row-1']", + ) as HTMLButtonElement | null; + await act(async () => { + oldRow?.click(); + }); + await flush(); + const restoreButtons = Array.from(container.querySelectorAll("button")).filter( + (button) => button.textContent === "Restore as new revision", + ); + await act(async () => { + restoreButtons[0].click(); + }); + await flush(); + const confirmButtons = Array.from(container.querySelectorAll("button")).filter((b) => + (b.textContent ?? "").includes("Restore as revision 3"), + ); + await act(async () => { + confirmButtons[0].click(); + }); + await flush(); + expect(onRestored).toHaveBeenCalledTimes(1); + const [response] = onRestored.mock.calls[0]; + expect(response.routine).toEqual(restoredRoutine); + expect(response.revision.id).toBe("revision-3"); + }); }); diff --git a/ui/src/components/RoutineHistoryTab.tsx b/ui/src/components/RoutineHistoryTab.tsx index 1e6ec6ec732..9a8ce841fac 100644 --- a/ui/src/components/RoutineHistoryTab.tsx +++ b/ui/src/components/RoutineHistoryTab.tsx @@ -48,6 +48,7 @@ type Props = { agents: AgentLookup; projects: ProjectLookup; onRestoreSecretMaterials: (response: RestoreRoutineRevisionResponse) => void; + onRestored?: (response: RestoreRoutineRevisionResponse) => void; }; export function RoutineHistoryTab({ @@ -59,6 +60,7 @@ export function RoutineHistoryTab({ agents, projects, onRestoreSecretMaterials, + onRestored, }: Props) { const queryClient = useQueryClient(); const { pushToast } = useToastActions(); @@ -116,6 +118,7 @@ export function RoutineHistoryTab({ tone: "success", }); onRestoreSecretMaterials(data); + onRestored?.(data); setConfirmOpen(false); setRestoreSummary(""); setSelectedRevisionId(data.revision.id); diff --git a/ui/src/pages/RoutineDetail.tsx b/ui/src/pages/RoutineDetail.tsx index ceedff8a141..047d515f446 100644 --- a/ui/src/pages/RoutineDetail.tsx +++ b/ui/src/pages/RoutineDetail.tsx @@ -63,7 +63,7 @@ import { } from "@/components/ui/select"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Badge } from "@/components/ui/badge"; -import type { RoutineTrigger, RoutineVariable } from "@paperclipai/shared"; +import type { RoutineDetail as RoutineDetailType, RoutineTrigger, RoutineVariable } from "@paperclipai/shared"; const concurrencyPolicies = ["coalesce_if_active", "always_enqueue", "skip_if_active"]; const catchUpPolicies = ["skip_missed", "enqueue_missed_with_cap"]; @@ -1236,6 +1236,32 @@ export function RoutineDetail() { }); } }} + onRestored={(response: RestoreRoutineRevisionResponse) => { + setSaveConflict(false); + queryClient.setQueryData( + queryKeys.routines.detail(routineId!), + (prev) => + prev + ? { + ...prev, + ...response.routine, + latestRevisionId: response.revision.id, + latestRevisionNumber: response.revision.revisionNumber, + } + : prev, + ); + setEditDraft({ + title: response.routine.title, + description: response.routine.description ?? "", + projectId: response.routine.projectId ?? "", + assigneeAgentId: response.routine.assigneeAgentId ?? "", + priority: response.routine.priority, + concurrencyPolicy: response.routine.concurrencyPolicy, + catchUpPolicy: response.routine.catchUpPolicy, + variables: response.routine.variables, + }); + hydratedRoutineIdRef.current = response.routine.id; + }} />