diff --git a/webview-ui/src/components/chat/ChangeCard.tsx b/webview-ui/src/components/chat/ChangeCard.tsx index 709b4e6fb9..86393f7c9c 100644 --- a/webview-ui/src/components/chat/ChangeCard.tsx +++ b/webview-ui/src/components/chat/ChangeCard.tsx @@ -1,7 +1,7 @@ import { useEffect, useMemo, useState } from "react" import { useTranslation } from "react-i18next" import { VSCodeProgressRing } from "@vscode/webview-ui-toolkit/react" -import { Check, FileDiff, RotateCcw, X } from "lucide-react" +import { Check, FileDiff, History, RotateCcw, X } from "lucide-react" import { safeJsonParse } from "@roo/core" import { changeCardSchema, type ClineMessage, type ExtensionMessage } from "@roo-code/types" @@ -25,10 +25,14 @@ const successState: RollbackState = { status: "success" } /** * Per-step change card (B3a payload, B3b UI): header with the file count, a - * per-file list with +/− diff badges, and per-file / per-step rollback + * per-file list with +/− diff badges, and per-file / per-step restore * controls wired to the extension host through the - * `checkpointRollbackFile` / `checkpointRollbackStep` messages. Diffs come from - * the payload's per-file `diff` field: `full` cards expand by default, + * `checkpointRollbackFile` / `checkpointRollbackStep` / + * `checkpointRestoreLatestFile` messages. A rollback restores a file to the + * state it had BEFORE the step (undoing the step's write); a restore-latest + * brings the file back to its most recent recorded version (the forward + * direction, available from every card of the task). Diffs come from the + * payload's per-file `diff` field: `full` cards expand by default, * `summary` cards expand lazily on toggle, compact cards carry no diff. */ export const ChangeCard = ({ message }: { message: ClineMessage }) => { @@ -64,11 +68,12 @@ export const ChangeCard = ({ message }: { message: ClineMessage }) => { }) const [fileRollbacks, setFileRollbacks] = useState>({}) + const [fileRestores, setFileRestores] = useState>({}) const [stepRollback, setStepRollback] = useState(IDLE) const checkpointId = card?.checkpointIds[0] - // Correlate extension rollback results with this card by message ts. + // Correlate extension restore results with this card by message ts. useEffect(() => { const handler = (event: MessageEvent) => { const data = event.data as ExtensionMessage | undefined @@ -85,10 +90,16 @@ export const ChangeCard = ({ message }: { message: ClineMessage }) => { // branch being taken or skipped is unobservable. // Stryker disable next-line ConditionalExpression: undefined-path branch unobservable if (filePath !== undefined) { - setFileRollbacks((prev) => ({ - ...prev, - [filePath]: result.success ? successState : { status: "error", error: result.error }, - })) + // Per-file results route to the control that requested them: + // restore-latest results update the restore control; everything + // else (including results posted before `kind` existed) the + // rollback control. + const state: RollbackState = result.success ? successState : { status: "error", error: result.error } + if (result.kind === "restore-latest") { + setFileRestores((prev) => ({ ...prev, [filePath]: state })) + } else { + setFileRollbacks((prev) => ({ ...prev, [filePath]: state })) + } } if (result.files) { const fileUpdates: Record = {} @@ -138,6 +149,14 @@ export const ChangeCard = ({ message }: { message: ClineMessage }) => { setFileRollbacks((prev) => ({ ...prev, [path]: { status: "pending" } })) } + const requestFileRestoreLatest = (path: string) => { + vscode.postMessage({ + type: "checkpointRestoreLatestFile", + payload: { cardTs: message.ts, filePath: path }, + }) + setFileRestores((prev) => ({ ...prev, [path]: { status: "pending" } })) + } + const requestStepRollback = () => { vscode.postMessage({ type: "checkpointRollbackStep", @@ -170,6 +189,9 @@ export const ChangeCard = ({ message }: { message: ClineMessage }) => { case "confirming": return ( + + {t("chat:changeCard.rollbackFileWarning")} + + + + ) + case "pending": + return ( + + + + ) + case "success": + return ( + + + {t("chat:changeCard.restored")} + + ) + case "error": + return ( + + + + {t("chat:changeCard.restoreFailed")} + + + ) + default: + return ( + + + + ) + } + } + const stepRollbackControls = () => { switch (stepRollback.status) { case "confirming": @@ -342,7 +435,10 @@ export const ChangeCard = ({ message }: { message: ClineMessage }) => { )} -
{fileRollbackControls(file.path, index)}
+
+ {fileRollbackControls(file.path, index)} + {fileRestoreLatestControls(file.path, index)} +
))} diff --git a/webview-ui/src/components/chat/__tests__/ChangeCard.spec.tsx b/webview-ui/src/components/chat/__tests__/ChangeCard.spec.tsx index b8718b05d1..b3b9fc408f 100644 --- a/webview-ui/src/components/chat/__tests__/ChangeCard.spec.tsx +++ b/webview-ui/src/components/chat/__tests__/ChangeCard.spec.tsx @@ -19,6 +19,11 @@ vi.mock("react-i18next", () => ({ const map: Record = { "chat:changeCard.header": `${options?.count ?? 0} file(s) changed this step`, "chat:changeCard.rollbackFile": "Rollback this file", + "chat:changeCard.rollbackFileWarning": "Restores this file to the content it had before this step.", + "chat:changeCard.restoreLatest": "Restore latest version", + "chat:changeCard.restoreLatestWarning": "Restores this file to the latest recorded version.", + "chat:changeCard.restored": "Restored latest version", + "chat:changeCard.restoreFailed": "Restore failed", "chat:changeCard.rollbackStep": "Rollback step", "chat:changeCard.rollbackWarning": "Restores the previous content of this step's files.", "chat:changeCard.confirm": "Confirm", @@ -403,6 +408,115 @@ describe("ChangeCard", () => { expect(control).toHaveAttribute("aria-label", "Open file") expect(control).toHaveAttribute("title", "Open file") }) + + it("restores one file to the latest version through checkpointRestoreLatestFile and shows pending + success", async () => { + renderWithExtensionState() + + // Open the confirm step for the restore-latest control. + fireEvent.click(screen.getByTestId("change-card-file-restore-0")) + expect(screen.getByTestId("change-card-file-restore-confirm-0")).toBeInTheDocument() + expect(screen.getByText("Restores this file to the latest recorded version.")).toBeInTheDocument() + + // Confirm sends the webview->extension message and goes pending. + fireEvent.click(screen.getByText("Confirm")) + expect(mockPostMessage).toHaveBeenCalledWith({ + type: "checkpointRestoreLatestFile", + payload: { cardTs: 1000, filePath: "src/a.ts" }, + }) + expect(screen.getByTestId("change-card-file-restore-pending-0")).toBeInTheDocument() + + // The extension ack (kind "restore-latest") resolves the pending state. + fireRollbackResult({ + type: "checkpointRollbackResult", + checkpointRollbackResult: { + cardTs: 1000, + kind: "restore-latest", + filePath: "src/a.ts", + success: true, + }, + }) + expect(await screen.findByTestId("change-card-file-restore-success-0")).toHaveTextContent( + "Restored latest version", + ) + }) + + it("shows the restore-latest error state on a failed ack", async () => { + renderWithExtensionState() + + fireEvent.click(screen.getByTestId("change-card-file-restore-1")) + fireEvent.click(screen.getByText("Confirm")) + + fireRollbackResult({ + type: "checkpointRollbackResult", + checkpointRollbackResult: { + cardTs: 1000, + kind: "restore-latest", + filePath: "src/b.ts", + success: false, + error: "checkpoint not found", + }, + }) + + expect(await screen.findByTestId("change-card-file-restore-error-1")).toHaveTextContent("Restore failed") + }) + + it("treats a no-op restore-latest (no recorded write) as a success", async () => { + renderWithExtensionState() + + fireEvent.click(screen.getByTestId("change-card-file-restore-0")) + fireEvent.click(screen.getByText("Confirm")) + + fireRollbackResult({ + type: "checkpointRollbackResult", + checkpointRollbackResult: { + cardTs: 1000, + kind: "restore-latest", + filePath: "src/a.ts", + success: true, + noOp: true, + }, + }) + + expect(await screen.findByTestId("change-card-file-restore-success-0")).toBeInTheDocument() + }) + + it("keeps the rollback and restore-latest controls independent on correlated results", async () => { + renderWithExtensionState() + + // A rollback result (no kind: the legacy shape) updates only the + // rollback control of the file. + fireRollbackResult({ + type: "checkpointRollbackResult", + checkpointRollbackResult: { cardTs: 1000, filePath: "src/a.ts", success: true }, + }) + expect(await screen.findByTestId("change-card-file-success-0")).toBeInTheDocument() + expect(screen.getByTestId("change-card-file-restore-0")).toBeInTheDocument() + + // A restore-latest result updates only the restore control. + fireRollbackResult({ + type: "checkpointRollbackResult", + checkpointRollbackResult: { + cardTs: 1000, + kind: "restore-latest", + filePath: "src/a.ts", + success: true, + }, + }) + expect(await screen.findByTestId("change-card-file-restore-success-0")).toBeInTheDocument() + // The rollback control keeps its own success state (not overwritten). + expect(screen.getByTestId("change-card-file-success-0")).toBeInTheDocument() + }) + + it("cancels the file restore-latest confirmation without sending a message", () => { + renderWithExtensionState() + + fireEvent.click(screen.getByTestId("change-card-file-restore-0")) + fireEvent.click(screen.getByTestId("change-card-file-restore-cancel-0")) + expect(screen.getByTestId("change-card-file-restore-0")).toBeInTheDocument() + expect(mockPostMessage).not.toHaveBeenCalledWith( + expect.objectContaining({ type: "checkpointRestoreLatestFile" }), + ) + }) }) describe("ChatRow - change_card say", () => { @@ -575,8 +689,10 @@ describe("ChangeCard - mutation coverage (round 2)", () => { />, ) - // Idle: the tooltip explains the rollback affordance. - expect(screen.getByTestId("cc-tooltip-content")).toHaveTextContent("Rollback this file") + // Idle: the tooltip explains the rollback affordance. The file row + // also carries the restore-latest tooltip, so match by content. + const tooltips = () => screen.getAllByTestId("cc-tooltip-content") + expect(tooltips().some((el) => el.textContent?.trim() === "Rollback this file")).toBe(true) fireEvent.click(screen.getByTestId("change-card-file-rollback-0")) fireEvent.click(screen.getByText("Confirm")) @@ -590,7 +706,7 @@ describe("ChangeCard - mutation coverage (round 2)", () => { }, }) await screen.findByTestId("change-card-file-error-0") - expect(screen.getByTestId("cc-tooltip-content")).toHaveTextContent("checkpoint not found") + expect(tooltips().some((el) => el.textContent?.trim() === "checkpoint not found")).toBe(true) }) it("falls back to the localized failure label when the file error carries no detail", async () => { @@ -607,7 +723,11 @@ describe("ChangeCard - mutation coverage (round 2)", () => { checkpointRollbackResult: { cardTs: 1000, filePath: "src/a.ts", success: false }, }) await screen.findByTestId("change-card-file-error-0") - expect(screen.getByTestId("cc-tooltip-content")).toHaveTextContent("Rollback failed") + // The file row also carries the restore-latest tooltip, so match by + // content: the rollback tooltip must show the localized fallback. + expect( + screen.getAllByTestId("cc-tooltip-content").some((el) => el.textContent?.trim() === "Rollback failed"), + ).toBe(true) }) it("resolves the step error with the first failing file's detail, not the outer error", async () => { @@ -726,4 +846,67 @@ describe("ChangeCard - mutation coverage (round 2)", () => { const { container } = renderWithExtensionState() expect(container.innerHTML).toBe("") }) + + it("shows the per-file rollback warning copy in the file confirm step", () => { + renderWithExtensionState() + + fireEvent.click(screen.getByTestId("change-card-file-rollback-0")) + + expect(screen.getByText("Restores this file to the content it had before this step.")).toBeInTheDocument() + }) + + it("announces the restore-latest affordance through the tooltip and the aria-label", () => { + renderWithExtensionState() + + // The file row also carries the rollback tooltip, so match by content. + expect( + screen + .getAllByTestId("cc-tooltip-content") + .some((el) => el.textContent?.trim() === "Restore latest version"), + ).toBe(true) + expect(screen.getByTestId("change-card-file-restore-0")).toHaveAttribute("aria-label", "Restore latest version") + }) + + it("shows the localized cancel label in the restore-latest confirm step", () => { + renderWithExtensionState() + + fireEvent.click(screen.getByTestId("change-card-file-restore-0")) + + expect(screen.getByTestId("change-card-file-restore-cancel-0")).toHaveTextContent("Cancel") + }) + + it("shows the restore error tooltip: the localized fallback without an error, verbatim otherwise", async () => { + renderWithExtensionState() + + // File 0: the failure carries no error -> the localized fallback label. + fireEvent.click(screen.getByTestId("change-card-file-restore-0")) + fireEvent.click(screen.getByText("Confirm")) + fireRollbackResult({ + type: "checkpointRollbackResult", + checkpointRollbackResult: { cardTs: 1000, kind: "restore-latest", filePath: "src/a.ts", success: false }, + }) + await screen.findByTestId("change-card-file-restore-error-0") + expect( + screen.getAllByTestId("cc-tooltip-content").some((el) => el.textContent?.trim() === "Restore failed"), + ).toBe(true) + + // File 1: an empty-string error is not nullish, so the tooltip carries + // the extension's (empty) value, not the fallback (`??` semantics). + fireEvent.click(screen.getByTestId("change-card-file-restore-1")) + fireEvent.click(screen.getByText("Confirm")) + fireRollbackResult({ + type: "checkpointRollbackResult", + checkpointRollbackResult: { + cardTs: 1000, + kind: "restore-latest", + filePath: "src/b.ts", + success: false, + error: "", + }, + }) + await screen.findByTestId("change-card-file-restore-error-1") + expect(screen.getAllByTestId("cc-tooltip-content").some((el) => (el.textContent ?? "").trim() === "")).toBe( + true, + ) + }) }) diff --git a/webview-ui/src/i18n/locales/ca/chat.json b/webview-ui/src/i18n/locales/ca/chat.json index 8debf753c6..941be19e88 100644 --- a/webview-ui/src/i18n/locales/ca/chat.json +++ b/webview-ui/src/i18n/locales/ca/chat.json @@ -170,6 +170,11 @@ "changeCard": { "header": "{{count}} fitxer(s) canviat(s) en aquest pas", "rollbackFile": "Revertir aquest fitxer", + "rollbackFileWarning": "Restaura aquest fitxer al contingut que tenia abans d'aquest pas.", + "restoreLatest": "Restaura l'última versió", + "restoreLatestWarning": "Restaura aquest fitxer a l'última versió registrada.", + "restored": "Última versió restaurada", + "restoreFailed": "Error en restaurar", "rollbackStep": "Revertir el pas", "rollbackWarning": "Restaura el contingut anterior dels fitxers d'aquest pas.", "confirm": "Confirmar", diff --git a/webview-ui/src/i18n/locales/de/chat.json b/webview-ui/src/i18n/locales/de/chat.json index b057d23789..aa340fa71c 100644 --- a/webview-ui/src/i18n/locales/de/chat.json +++ b/webview-ui/src/i18n/locales/de/chat.json @@ -170,6 +170,11 @@ "changeCard": { "header": "{{count}} Datei(en) in diesem Schritt geändert", "rollbackFile": "Diese Datei zurücksetzen", + "rollbackFileWarning": "Stellt diese Datei auf den Inhalt vor diesem Schritt wieder her.", + "restoreLatest": "Letzte Version wiederherstellen", + "restoreLatestWarning": "Stellt diese Datei auf die zuletzt aufgezeichnete Version wieder her.", + "restored": "Letzte Version wiederhergestellt", + "restoreFailed": "Wiederherstellung fehlgeschlagen", "rollbackStep": "Schritt zurücksetzen", "rollbackWarning": "Stellt den vorherigen Inhalt der Dateien dieses Schritts wieder her.", "confirm": "Bestätigen", diff --git a/webview-ui/src/i18n/locales/en/chat.json b/webview-ui/src/i18n/locales/en/chat.json index 2ede844eca..5da8edab2f 100644 --- a/webview-ui/src/i18n/locales/en/chat.json +++ b/webview-ui/src/i18n/locales/en/chat.json @@ -192,6 +192,11 @@ "changeCard": { "header": "{{count}} file(s) changed this step", "rollbackFile": "Rollback this file", + "rollbackFileWarning": "Restores this file to the content it had before this step.", + "restoreLatest": "Restore latest version", + "restoreLatestWarning": "Restores this file to the latest recorded version.", + "restored": "Restored latest version", + "restoreFailed": "Restore failed", "rollbackStep": "Rollback step", "rollbackWarning": "Restores the previous content of this step's files.", "confirm": "Confirm", diff --git a/webview-ui/src/i18n/locales/es/chat.json b/webview-ui/src/i18n/locales/es/chat.json index 923ccbee5e..fa3f993f2d 100644 --- a/webview-ui/src/i18n/locales/es/chat.json +++ b/webview-ui/src/i18n/locales/es/chat.json @@ -170,6 +170,11 @@ "changeCard": { "header": "{{count}} archivo(s) cambiado(s) en este paso", "rollbackFile": "Revertir este archivo", + "rollbackFileWarning": "Restaura este archivo al contenido que tenía antes de este paso.", + "restoreLatest": "Restaurar la última versión", + "restoreLatestWarning": "Restaura este archivo a la última versión registrada.", + "restored": "Última versión restaurada", + "restoreFailed": "Error al restaurar", "rollbackStep": "Revertir paso", "rollbackWarning": "Restaura el contenido anterior de los archivos de este paso.", "confirm": "Confirmar", diff --git a/webview-ui/src/i18n/locales/fr/chat.json b/webview-ui/src/i18n/locales/fr/chat.json index 2aaf3b0f1d..162ef0b27c 100644 --- a/webview-ui/src/i18n/locales/fr/chat.json +++ b/webview-ui/src/i18n/locales/fr/chat.json @@ -170,6 +170,11 @@ "changeCard": { "header": "{{count}} fichier(s) modifié(s) à cette étape", "rollbackFile": "Réinitialiser ce fichier", + "rollbackFileWarning": "Restaure ce fichier à son contenu avant cette étape.", + "restoreLatest": "Restaurer la dernière version", + "restoreLatestWarning": "Restaure ce fichier à la dernière version enregistrée.", + "restored": "Dernière version restaurée", + "restoreFailed": "Échec de la restauration", "rollbackStep": "Réinitialiser l'étape", "rollbackWarning": "Restaure le contenu précédent des fichiers de cette étape.", "confirm": "Confirmer", diff --git a/webview-ui/src/i18n/locales/hi/chat.json b/webview-ui/src/i18n/locales/hi/chat.json index bf8ebe5935..38c63f9412 100644 --- a/webview-ui/src/i18n/locales/hi/chat.json +++ b/webview-ui/src/i18n/locales/hi/chat.json @@ -170,6 +170,11 @@ "changeCard": { "header": "इस चरण में {{count}} फ़ाइल(ें) बदली गईं", "rollbackFile": "इस फ़ाइल को रोलबैक करें", + "rollbackFileWarning": "इस फ़ाइल को इस चरण से पहले की सामग्री पर पुनर्स्थापित करता है।", + "restoreLatest": "नवीनतम संस्करण पुनर्स्थापित करें", + "restoreLatestWarning": "इस फ़ाइल को नवीनतम दर्ज संस्करण पर पुनर्स्थापित करता है।", + "restored": "नवीनतम संस्करण पुनर्स्थापित हुआ", + "restoreFailed": "पुनर्स्थापना विफल", "rollbackStep": "चरण रोलबैक करें", "rollbackWarning": "यह चरण की फ़ाइलों की पिछली सामग्री पुनर्स्थापित करता है।", "confirm": "पुष्टि करें", diff --git a/webview-ui/src/i18n/locales/id/chat.json b/webview-ui/src/i18n/locales/id/chat.json index 48dedae853..01e76b0db6 100644 --- a/webview-ui/src/i18n/locales/id/chat.json +++ b/webview-ui/src/i18n/locales/id/chat.json @@ -195,6 +195,11 @@ "changeCard": { "header": "{{count}} file diubah pada langkah ini", "rollbackFile": "Kembalikan file ini", + "rollbackFileWarning": "Memulihkan file ini ke konten yang ada sebelum langkah ini.", + "restoreLatest": "Pulihkan versi terbaru", + "restoreLatestWarning": "Memulihkan file ini ke versi terbaru yang tercatat.", + "restored": "Versi terbaru dipulihkan", + "restoreFailed": "Gagal memulihkan", "rollbackStep": "Kembalikan langkah", "rollbackWarning": "Memulihkan konten sebelumnya dari file pada langkah ini.", "confirm": "Konfirmasi", diff --git a/webview-ui/src/i18n/locales/it/chat.json b/webview-ui/src/i18n/locales/it/chat.json index 84bbabc405..6c21a9dee3 100644 --- a/webview-ui/src/i18n/locales/it/chat.json +++ b/webview-ui/src/i18n/locales/it/chat.json @@ -173,6 +173,11 @@ "changeCard": { "header": "{{count}} file modificati in questo passaggio", "rollbackFile": "Annulla modifiche a questo file", + "rollbackFileWarning": "Ripristina questo file al contenuto che aveva prima di questo passaggio.", + "restoreLatest": "Ripristina l'ultima versione", + "restoreLatestWarning": "Ripristina questo file all'ultima versione registrata.", + "restored": "Ultima versione ripristinata", + "restoreFailed": "Ripristino non riuscito", "rollbackStep": "Annulla modifiche del passaggio", "rollbackWarning": "Ripristina il contenuto precedente dei file di questo passaggio.", "confirm": "Conferma", diff --git a/webview-ui/src/i18n/locales/ja/chat.json b/webview-ui/src/i18n/locales/ja/chat.json index 3784682ebd..4a25d13347 100644 --- a/webview-ui/src/i18n/locales/ja/chat.json +++ b/webview-ui/src/i18n/locales/ja/chat.json @@ -170,6 +170,11 @@ "changeCard": { "header": "このステップで {{count}} 件のファイルが変更されました", "rollbackFile": "このファイルをロールバック", + "rollbackFileWarning": "このファイルをこのステップ実行前の内容に復元します。", + "restoreLatest": "最新バージョンを復元", + "restoreLatestWarning": "このファイルを最新記録バージョンに復元します。", + "restored": "最新バージョンを復元しました", + "restoreFailed": "復元に失敗しました", "rollbackStep": "ステップをロールバック", "rollbackWarning": "このステップのファイルを元のコンテンツに復元します。", "confirm": "確認", diff --git a/webview-ui/src/i18n/locales/ko/chat.json b/webview-ui/src/i18n/locales/ko/chat.json index 253312f3e0..ef82154d39 100644 --- a/webview-ui/src/i18n/locales/ko/chat.json +++ b/webview-ui/src/i18n/locales/ko/chat.json @@ -170,6 +170,11 @@ "changeCard": { "header": "이 단계에서 {{count}}개 파일이 변경됨", "rollbackFile": "이 파일 되돌리기", + "rollbackFileWarning": "이 파일을 이 단계 실행 전 내용으로 복원합니다.", + "restoreLatest": "최신 버전 복원", + "restoreLatestWarning": "이 파일을 최신 기록 버전으로 복원합니다.", + "restored": "최신 버전이 복원됨", + "restoreFailed": "복원 실패", "rollbackStep": "단계 되돌리기", "rollbackWarning": "이 단계 파일의 이전 콘텐츠로 복원합니다.", "confirm": "확인", diff --git a/webview-ui/src/i18n/locales/nl/chat.json b/webview-ui/src/i18n/locales/nl/chat.json index 11b4518719..cd6106bb20 100644 --- a/webview-ui/src/i18n/locales/nl/chat.json +++ b/webview-ui/src/i18n/locales/nl/chat.json @@ -165,6 +165,11 @@ "changeCard": { "header": "{{count}} bestand(en) gewijzigd in deze stap", "rollbackFile": "Dit bestand terugzetten", + "rollbackFileWarning": "Stelt dit bestand terug naar de inhoud vóór deze stap.", + "restoreLatest": "Laatste versie herstellen", + "restoreLatestWarning": "Stelt dit bestand terug naar de laatst geregistreerde versie.", + "restored": "Laatste versie hersteld", + "restoreFailed": "Herstellen mislukt", "rollbackStep": "Stap terugzetten", "rollbackWarning": "Stelt de eerdere inhoud van de bestanden van deze stap weer in.", "confirm": "Bevestigen", diff --git a/webview-ui/src/i18n/locales/pl/chat.json b/webview-ui/src/i18n/locales/pl/chat.json index dd201ce574..9ac1358c50 100644 --- a/webview-ui/src/i18n/locales/pl/chat.json +++ b/webview-ui/src/i18n/locales/pl/chat.json @@ -170,6 +170,11 @@ "changeCard": { "header": "{{count}} plik(ów) zmienionych w tym kroku", "rollbackFile": "Cofnij ten plik", + "rollbackFileWarning": "Przywraca ten plik do zawartości sprzed tego kroku.", + "restoreLatest": "Przywróć najnowszą wersję", + "restoreLatestWarning": "Przywraca ten plik do najnowszej zarejestrowanej wersji.", + "restored": "Przywrócono najnowszą wersję", + "restoreFailed": "Przywracanie nie powiodło się", "rollbackStep": "Cofnij krok", "rollbackWarning": "Przywraca poprzednią zawartość plików tego kroku.", "confirm": "Potwierdź", diff --git a/webview-ui/src/i18n/locales/pt-BR/chat.json b/webview-ui/src/i18n/locales/pt-BR/chat.json index 6a9d5a64fe..58b41faf0d 100644 --- a/webview-ui/src/i18n/locales/pt-BR/chat.json +++ b/webview-ui/src/i18n/locales/pt-BR/chat.json @@ -170,6 +170,11 @@ "changeCard": { "header": "{{count}} arquivo(s) alterado(s) nesta etapa", "rollbackFile": "Reverter este arquivo", + "rollbackFileWarning": "Restaura este arquivo para o conteúdo que tinha antes desta etapa.", + "restoreLatest": "Restaurar última versão", + "restoreLatestWarning": "Restaura este arquivo para a última versão registrada.", + "restored": "Última versão restaurada", + "restoreFailed": "Falha ao restaurar", "rollbackStep": "Reverter etapa", "rollbackWarning": "Restaura o conteúdo anterior dos arquivos desta etapa.", "confirm": "Confirmar", diff --git a/webview-ui/src/i18n/locales/ru/chat.json b/webview-ui/src/i18n/locales/ru/chat.json index a73537bd7a..9c27a6f057 100644 --- a/webview-ui/src/i18n/locales/ru/chat.json +++ b/webview-ui/src/i18n/locales/ru/chat.json @@ -165,6 +165,11 @@ "changeCard": { "header": "В этом шаге изменено файлов: {{count}}", "rollbackFile": "Откатить этот файл", + "rollbackFileWarning": "Восстанавливает файл к содержимому, которое было до этого шага.", + "restoreLatest": "Восстановить последнюю версию", + "restoreLatestWarning": "Восстанавливает файл к последней записанной версии.", + "restored": "Последняя версия восстановлена", + "restoreFailed": "Ошибка восстановления", "rollbackStep": "Откатить шаг", "rollbackWarning": "Восстанавливает предыдущее содержимое файлов этого шага.", "confirm": "Подтвердить", diff --git a/webview-ui/src/i18n/locales/tr/chat.json b/webview-ui/src/i18n/locales/tr/chat.json index 3268d642a1..68399a1093 100644 --- a/webview-ui/src/i18n/locales/tr/chat.json +++ b/webview-ui/src/i18n/locales/tr/chat.json @@ -170,6 +170,11 @@ "changeCard": { "header": "Bu adımda {{count}} dosya değiştirildi", "rollbackFile": "Bu dosyayı geri al", + "rollbackFileWarning": "Bu dosyayı bu adım öncesi içeriğine geri yükler.", + "restoreLatest": "Son sürümü geri yükle", + "restoreLatestWarning": "Bu dosyayı son kaydedilmiş sürüme geri yükler.", + "restored": "Son sürüm geri yüklendi", + "restoreFailed": "Geri yükleme başarısız", "rollbackStep": "Adımı geri al", "rollbackWarning": "Bu adımın dosyalarının önceki içeriğini geri yükler.", "confirm": "Onayla", diff --git a/webview-ui/src/i18n/locales/vi/chat.json b/webview-ui/src/i18n/locales/vi/chat.json index cfc4cc7e8f..cecd571863 100644 --- a/webview-ui/src/i18n/locales/vi/chat.json +++ b/webview-ui/src/i18n/locales/vi/chat.json @@ -170,6 +170,11 @@ "changeCard": { "header": "{{count}} tệp đã thay đổi trong bước này", "rollbackFile": "Hoàn tác tệp này", + "rollbackFileWarning": "Khôi phục tệp này về nội dung trước bước này.", + "restoreLatest": "Khôi phục phiên bản mới nhất", + "restoreLatestWarning": "Khôi phục tệp này về phiên bản được ghi lại gần nhất.", + "restored": "Đã khôi phục phiên bản mới nhất", + "restoreFailed": "Khôi phục thất bại", "rollbackStep": "Hoàn tác bước", "rollbackWarning": "Khôi phục nội dung trước đó của các tệp trong bước này.", "confirm": "Xác nhận", diff --git a/webview-ui/src/i18n/locales/zh-CN/chat.json b/webview-ui/src/i18n/locales/zh-CN/chat.json index 7475f353b2..0d45c0826c 100644 --- a/webview-ui/src/i18n/locales/zh-CN/chat.json +++ b/webview-ui/src/i18n/locales/zh-CN/chat.json @@ -170,6 +170,11 @@ "changeCard": { "header": "此步骤中更改了 {{count}} 个文件", "rollbackFile": "回退此文件", + "rollbackFileWarning": "将此文件还原到此步骤执行前的内容。", + "restoreLatest": "还原至最新版本", + "restoreLatestWarning": "将此文件还原至最近一次记录的版本。", + "restored": "已还原至最新版本", + "restoreFailed": "还原失败", "rollbackStep": "回退此步骤", "rollbackWarning": "将恢复此步骤文件的上一版本内容。", "confirm": "确认", diff --git a/webview-ui/src/i18n/locales/zh-TW/chat.json b/webview-ui/src/i18n/locales/zh-TW/chat.json index 0ca9db1498..0e5c4cb3d7 100644 --- a/webview-ui/src/i18n/locales/zh-TW/chat.json +++ b/webview-ui/src/i18n/locales/zh-TW/chat.json @@ -192,6 +192,11 @@ "changeCard": { "header": "此步驟中變更了 {{count}} 個檔案", "rollbackFile": "還原此檔案", + "rollbackFileWarning": "將此檔案還原到此步驟執行前的內容。", + "restoreLatest": "還原至最新版本", + "restoreLatestWarning": "將此檔案還原至最近一次記錄的版本。", + "restored": "已還原至最新版本", + "restoreFailed": "還原失敗", "rollbackStep": "還原此步驟", "rollbackWarning": "將還原此步驟檔案的上一版內容。", "confirm": "確認",