# Phase 4: UI Restore button + ConfirmDialog + i18n en+ru **Status:** ⬜ Not Started **Parent plan:** [PLAN.md](./PLAN.md) **Domain:** frontend Built by the **frontend implementer agent**. Must follow project conventions: Svelte 5 runes, `ConfirmDialog` for the destructive action (NEVER `window.confirm`), `$t` with **en+ru parity**, the existing `.panel`/`.forge-btn-ghost` vocabulary in `WorkloadSnapshotsPanel.svelte`. ## Tasks - [ ] **`web/src/lib/api.ts`**: `restoreSnapshot(workloadId: string, sid: string): Promise` — POST `/api/workloads/${workloadId}/snapshots/${sid}/restore` with header `X-Confirm-Restore: ${sid}`. Check how the DB restore sends its `X-Confirm-Restore` header and reuse that fetch mechanism (the typed `post` may need a header-capable variant or a raw `fetch` like `download` already uses). - [ ] **`WorkloadSnapshotsPanel.svelte`**: - add a **Restore** action per snapshot row (beside Download/Delete) → opens `ConfirmDialog`. - ConfirmDialog: strong destructive copy — title + message making clear it **overwrites live data and restarts the app**, and that a **pre-restore snapshot is auto-captured**; `confirmVariant="danger"`. - on confirm: call `restoreSnapshot`, show a "restoring…" busy state (disable row actions), toast success/failure, then `load()` to refresh. - update the file's top comment (currently "Restore is intentionally NOT here yet") to reflect that restore now ships. - [ ] **i18n**: add `apps.detail.snapshots.restore`, `.restoring`, `.restored`, `.restoreFailed`, `.confirmRestoreTitle`, `.confirmRestoreMessage` to BOTH `web/src/lib/i18n/en.json` and `web/src/lib/i18n/ru.json`. Verify parity manually (a missing key is NOT a build error — `$t` returns the key string). ## Files to Modify/Create - `web/src/lib/api.ts` — `restoreSnapshot` - `web/src/lib/components/WorkloadSnapshotsPanel.svelte` — Restore button + ConfirmDialog + busy state - `web/src/lib/i18n/en.json`, `web/src/lib/i18n/ru.json` — restore keys (parity) ## Acceptance Criteria - `npm run check` 0 errors; `npm run build` succeeds; `npm run test` green. - en/ru key parity equal (every new key in both files). - ConfirmDialog used (no native confirm/alert); danger variant; copy warns about data overwrite + app restart. - Restore button disabled while a restore is in flight. - Restart dev server (`./scripts/dev-server.sh`). ## Notes - Only image-source workloads expose snapshots, so no source-kind gating is needed in the panel beyond what already exists. - Keep the Restore button visually subordinate to Download but clearly destructive (danger styling) — it's the most dangerous action in the panel. ## Review Checklist - [ ] All tasks completed - [ ] Code follows project conventions (ToggleSwitch/ConfirmDialog rules, runes) - [ ] No unintended side effects - [ ] Build passes (`npm run check` + `npm run build`) - [ ] Tests pass; i18n parity verified ## Handoff to Next Phase Implemented: `api.restoreSnapshot(workloadId, sid)` (POST + `X-Confirm-Restore` header, mirrors `restoreBackup`); `WorkloadSnapshotsPanel.svelte` Restore button per row → `ConfirmDialog` (danger, warns: overwrites live data + restarts app + auto pre-restore snapshot) → `doRestore` with busy state (`restoringId` disables all row actions, active row shows "Restoring…"); i18n `apps.detail.snapshots.restore*` in en+ru (parity verified). Verify: `npm run check` 0 errors, `npm run build` OK, `npm run test` 26 pass; i18n parity equal; dev server restarted on :9000. typescript-reviewer: APPROVE (no blockers; one cosmetic wording note on the success toast addressed). Final phase — done.