fix(data-wipe): say what undoing a wipe does not bring back

A restore of the copy a data wipe took replaces the record with that copy,
so anything added after the wipe is gone, and the wipe ended integration
connections, sharing in both directions, Guardian roles and share links,
none of which are in a copy. Neither the person's confirmation nor the
admin's restore dialog said so. Both do now, in every locale.
This commit is contained in:
Marc-André Bombeck committed 2026-10-25 17:32:33 +01:00
1 parent 2b79c01952
commit 69c5c65376
12 files changed
+107 -29

No files matched your search

+4 -2
View File
@@ -7655,7 +7655,8 @@
"deleteAccountCardDetailGrace": "Dein Konto wird sofort gesperrt und nach {days} Tagen endgültig gelöscht. Bis dahin kann ein Admin dieses Servers die Löschung rückgängig machen.",
"deleteAccountConfirmDescriptionGrace": "Du wirst abgemeldet und dein Konto wird sofort gesperrt. Nach {days} Tagen wird es endgültig gelöscht, mit allen Gesundheitsdaten, Passkeys, Sessions und Audit-Einträgen. Bis dahin kann ein Admin dieses Servers die Löschung rückgängig machen. Kopien in den Sicherungen dieses Servers verfallen nach ihrer Aufbewahrungsfrist.",
"deleteAccountScheduled": "Dein Konto ist gesperrt und wird am {date} endgültig gelöscht. Du wirst abgemeldet.",
"dangerZoneSuccessGrace": "Deine Gesundheitsakte wurde gelöscht. Ein Admin dieses Servers kann sie bis zum {date} wiederherstellen."
"dangerZoneSuccessGrace": "Deine Gesundheitsakte wurde gelöscht. Ein Admin dieses Servers kann sie bis zum {date} wiederherstellen.",
"dangerZoneRestoreLimits": "Auch Zugriffe enden: was du mit anderen teilst, was andere mit dir teilen, und jede Rolle als betreuende Person. Eine Wiederherstellung bringt die Akte so zurück, wie sie jetzt ist. Was nach dem Löschen hinzukommt, wird dabei ersetzt, und Integrationsverbindungen, Freigaben, Betreuungsrollen und Freigabe-Links kommen nicht zurück; sie müssen neu eingerichtet werden."
},
"admin": {
"title": "Administration",
@@ -8352,7 +8353,8 @@
"offhostFailedTitle": "Der neueste Datenbank-Dump ist nicht im externen Bucket angekommen",
"offhostLastUpload": "Der neueste Dump im Bucket stammt vom {when}.",
"offhostNeverUploaded": "Bisher ist kein Datenbank-Dump im Bucket angekommen."
}
},
"restorePreWipeLimits": "Diese Kopie wurde vor dem Löschen der Daten des Kontos angelegt, und ihre Wiederherstellung macht das Löschen rückgängig, soweit eine Kopie das kann: Was das Konto seitdem hinzugefügt hat, wird ersetzt, und Integrationsverbindungen, Freigaben, Betreuungszugriffe und Freigabe-Links kommen nicht zurück."
},
"danger-zone": {
"title": "Gefahrenzone",
+4 -2
View File
@@ -7655,7 +7655,8 @@
"deleteAccountCardDetailGrace": "Your account is locked right away and erased after {days} days. Until then, an admin of this server can undo the deletion.",
"deleteAccountConfirmDescriptionGrace": "You are signed out and your account is locked right away. After {days} days it is erased for good, with all health data, passkeys, sessions and audit entries. Until then, an admin of this server can undo the deletion. Copies in this server's backups age out after their retention period.",
"deleteAccountScheduled": "Your account is locked and will be erased on {date}. Signing you out.",
"dangerZoneSuccessGrace": "Your health record has been deleted. An admin of this server can restore it until {date}."
"dangerZoneSuccessGrace": "Your health record has been deleted. An admin of this server can restore it until {date}.",
"dangerZoneRestoreLimits": "Access ends too: what you share with others, what they share with you, and any Guardian role. A restore puts the record back as it is now. Anything added after the deletion is replaced, and integration connections, sharing, Guardian roles and share links do not come back; they have to be set up again."
},
"admin": {
"title": "Administration",
@@ -8352,7 +8353,8 @@
"offhostFailedTitle": "The newest database dump did not reach the off-host bucket",
"offhostLastUpload": "The newest dump in the bucket is from {when}.",
"offhostNeverUploaded": "No database dump has reached the bucket yet."
}
},
"restorePreWipeLimits": "This copy was taken before the account's data was deleted, and restoring it undoes that deletion as far as a copy can: whatever the account added since is replaced, and integration connections, sharing and Guardian access, and share links do not come back."
},
"danger-zone": {
"title": "Danger zone",
+4 -2
View File
@@ -7655,7 +7655,8 @@
"deleteAccountCardDetailGrace": "Tu cuenta se bloquea de inmediato y se borra al cabo de {days} días. Hasta entonces, un administrador de este servidor puede deshacer la eliminación.",
"deleteAccountConfirmDescriptionGrace": "Se cierra tu sesión y tu cuenta se bloquea de inmediato. Al cabo de {days} días se borra para siempre, con todos tus datos de salud, llaves de acceso, sesiones y entradas de auditoría. Hasta entonces, un administrador de este servidor puede deshacer la eliminación. Las copias en las copias de seguridad de este servidor caducan al terminar su periodo de conservación.",
"deleteAccountScheduled": "Tu cuenta está bloqueada y se borrará el {date}. Cerrando tu sesión.",
"dangerZoneSuccessGrace": "Tu historial de salud se ha eliminado. Un administrador de este servidor puede restaurarlo hasta el {date}."
"dangerZoneSuccessGrace": "Tu historial de salud se ha eliminado. Un administrador de este servidor puede restaurarlo hasta el {date}.",
"dangerZoneRestoreLimits": "También termina el acceso: lo que compartes con otras personas, lo que ellas comparten contigo y cualquier papel de persona responsable. Una restauración devuelve el historial tal como está ahora. Lo que se añada después de la eliminación se sustituye, y las conexiones de integración, lo compartido, los papeles de persona responsable y los enlaces compartidos no vuelven; hay que configurarlos de nuevo."
},
"admin": {
"title": "Administration",
@@ -8352,7 +8353,8 @@
"offhostFailedTitle": "El volcado más reciente de la base de datos no llegó al bucket externo",
"offhostLastUpload": "El volcado más reciente del bucket es del {when}.",
"offhostNeverUploaded": "Todavía no ha llegado ningún volcado de la base de datos al bucket."
}
},
"restorePreWipeLimits": "Esta copia se tomó antes de que se eliminaran los datos de la cuenta, y restaurarla deshace esa eliminación hasta donde una copia puede: lo que la cuenta añadió desde entonces se sustituye, y las conexiones de integración, lo compartido, el acceso de personas responsables y los enlaces compartidos no vuelven."
},
"danger-zone": {
"title": "Zona de peligro",
+4 -2
View File
@@ -7655,7 +7655,8 @@
"deleteAccountCardDetailGrace": "Votre compte est verrouillé immédiatement et effacé au bout de {days} jours. D’ici là, un administrateur de ce serveur peut annuler la suppression.",
"deleteAccountConfirmDescriptionGrace": "Vous êtes déconnecté et votre compte est verrouillé immédiatement. Au bout de {days} jours, il est effacé définitivement, avec toutes vos données de santé, vos clés d’accès, vos sessions et vos entrées d’audit. D’ici là, un administrateur de ce serveur peut annuler la suppression. Les copies dans les sauvegardes de ce serveur expirent à la fin de leur durée de conservation.",
"deleteAccountScheduled": "Votre compte est verrouillé et sera effacé le {date}. Déconnexion en cours.",
"dangerZoneSuccessGrace": "Votre dossier de santé a été supprimé. Un administrateur de ce serveur peut le restaurer jusqu’au {date}."
"dangerZoneSuccessGrace": "Votre dossier de santé a été supprimé. Un administrateur de ce serveur peut le restaurer jusqu’au {date}.",
"dangerZoneRestoreLimits": "Les accès prennent fin aussi : ce que vous partagez avec d’autres, ce qu’ils partagent avec vous, et tout rôle de personne responsable. Une restauration remet le dossier tel qu’il est maintenant. Ce qui est ajouté après la suppression est remplacé, et les connexions d’intégration, les partages, les rôles de personne responsable et les liens de partage ne reviennent pas ; il faut les configurer à nouveau."
},
"admin": {
"title": "Administration",
@@ -8352,7 +8353,8 @@
"offhostFailedTitle": "Le dump le plus récent de la base de données n'est pas arrivé dans le bucket externe",
"offhostLastUpload": "Le dump le plus récent du bucket date du {when}.",
"offhostNeverUploaded": "Aucun dump de la base de données n'est encore arrivé dans le bucket."
}
},
"restorePreWipeLimits": "Cette copie a été prise avant la suppression des données du compte, et la restaurer annule cette suppression autant qu’une copie le peut : ce que le compte a ajouté depuis est remplacé, et les connexions d’intégration, les partages, l’accès des personnes responsables et les liens de partage ne reviennent pas."
},
"danger-zone": {
"title": "Zone de danger",
+4 -2
View File
@@ -7655,7 +7655,8 @@
"deleteAccountCardDetailGrace": "Il tuo account viene bloccato subito e cancellato dopo {days} giorni. Fino ad allora un amministratore di questo server può annullare l’eliminazione.",
"deleteAccountConfirmDescriptionGrace": "Vieni disconnesso e il tuo account viene bloccato subito. Dopo {days} giorni viene cancellato definitivamente, con tutti i dati sanitari, le passkey, le sessioni e le voci di audit. Fino ad allora un amministratore di questo server può annullare l’eliminazione. Le copie nei backup di questo server scadono al termine del loro periodo di conservazione.",
"deleteAccountScheduled": "Il tuo account è bloccato e verrà cancellato il {date}. Disconnessione in corso.",
"dangerZoneSuccessGrace": "La tua cartella sanitaria è stata eliminata. Un amministratore di questo server può ripristinarla fino al {date}."
"dangerZoneSuccessGrace": "La tua cartella sanitaria è stata eliminata. Un amministratore di questo server può ripristinarla fino al {date}.",
"dangerZoneRestoreLimits": "Finisce anche l’accesso: ciò che condividi con altri, ciò che altri condividono con te e ogni ruolo di persona responsabile. Un ripristino riporta la cartella com’è adesso. Ciò che si aggiunge dopo l’eliminazione viene sostituito, e le connessioni di integrazione, le condivisioni, i ruoli di persona responsabile e i link di condivisione non tornano; vanno configurati di nuovo."
},
"admin": {
"title": "Administration",
@@ -8352,7 +8353,8 @@
"offhostFailedTitle": "Il dump più recente del database non è arrivato nel bucket esterno",
"offhostLastUpload": "Il dump più recente nel bucket è del {when}.",
"offhostNeverUploaded": "Nessun dump del database è ancora arrivato nel bucket."
}
},
"restorePreWipeLimits": "Questa copia è stata fatta prima che i dati dell’account venissero eliminati, e ripristinarla annulla l’eliminazione per quanto una copia può: ciò che l’account ha aggiunto nel frattempo viene sostituito, e le connessioni di integrazione, le condivisioni, l’accesso delle persone responsabili e i link di condivisione non tornano."
},
"danger-zone": {
"title": "Zona pericolosa",
+4 -2
View File
@@ -7655,7 +7655,8 @@
"deleteAccountCardDetailGrace": "계정은 바로 잠기고 {days}일 뒤에 완전히 지워져요. 그때까지는 이 서버의 관리자가 삭제를 취소할 수 있어요.",
"deleteAccountConfirmDescriptionGrace": "바로 로그아웃되고 계정이 잠겨요. {days}일 뒤에는 모든 건강 데이터, 패스키, 세션, 감사 기록과 함께 완전히 지워져요. 그때까지는 이 서버의 관리자가 삭제를 취소할 수 있어요. 이 서버 백업에 남은 사본은 보관 기간이 지나면 사라져요.",
"deleteAccountScheduled": "계정이 잠겼고 {date}에 완전히 지워져요. 로그아웃하는 중이에요.",
"dangerZoneSuccessGrace": "건강 기록이 삭제됐어요. {date}까지는 이 서버의 관리자가 복원할 수 있어요."
"dangerZoneSuccessGrace": "건강 기록이 삭제됐어요. {date}까지는 이 서버의 관리자가 복원할 수 있어요.",
"dangerZoneRestoreLimits": "접근 권한도 끝나요: 다른 사람과 공유한 것, 다른 사람이 나와 공유한 것, 보호자 역할 모두요. 복원하면 기록이 지금 상태로 돌아와요. 삭제 후에 추가된 것은 대체되고, 연동 연결, 공유, 보호자 역할, 공유 링크는 돌아오지 않아서 다시 설정해야 해요."
},
"admin": {
"title": "관리자",
@@ -8327,7 +8328,8 @@
"offhostFailedTitle": "최신 데이터베이스 덤프가 외부 버킷에 도달하지 못했습니다",
"offhostLastUpload": "버킷에 있는 최신 덤프는 {when}의 것입니다.",
"offhostNeverUploaded": "아직 버킷에 도달한 데이터베이스 덤프가 없습니다."
}
},
"restorePreWipeLimits": "이 사본은 계정의 데이터를 삭제하기 전에 만들어졌고, 복원하면 사본이 할 수 있는 만큼 그 삭제를 되돌려요: 그 후에 계정에 추가된 것은 대체되고, 연동 연결, 공유, 보호자 접근, 공유 링크는 돌아오지 않아요."
},
"danger-zone": {
"title": "위험 구역",
+4 -2
View File
@@ -7655,7 +7655,8 @@
"deleteAccountCardDetailGrace": "Je account wordt meteen vergrendeld en na {days} dagen gewist. Tot dan kan een beheerder van deze server de verwijdering ongedaan maken.",
"deleteAccountConfirmDescriptionGrace": "Je wordt uitgelogd en je account wordt meteen vergrendeld. Na {days} dagen wordt het definitief gewist, met alle gezondheidsgegevens, passkeys, sessies en auditregels. Tot dan kan een beheerder van deze server de verwijdering ongedaan maken. Kopieën in de back-ups van deze server verlopen na hun bewaartermijn.",
"deleteAccountScheduled": "Je account is vergrendeld en wordt op {date} gewist. Je wordt uitgelogd.",
"dangerZoneSuccessGrace": "Je gezondheidsdossier is verwijderd. Een beheerder van deze server kan het tot {date} terugzetten."
"dangerZoneSuccessGrace": "Je gezondheidsdossier is verwijderd. Een beheerder van deze server kan het tot {date} terugzetten.",
"dangerZoneRestoreLimits": "Ook toegang eindigt: wat je met anderen deelt, wat anderen met jou delen en elke rol als verzorger. Terugzetten brengt het dossier terug zoals het nu is. Wat na het verwijderen is toegevoegd, wordt vervangen, en koppelingen, delen, verzorgersrollen en deellinks komen niet terug; die moeten opnieuw worden ingesteld."
},
"admin": {
"title": "Beheer",
@@ -8352,7 +8353,8 @@
"offhostFailedTitle": "De nieuwste databasedump is niet in de externe bucket aangekomen",
"offhostLastUpload": "De nieuwste dump in de bucket is van {when}.",
"offhostNeverUploaded": "Er is nog geen databasedump in de bucket aangekomen."
}
},
"restorePreWipeLimits": "Deze kopie is gemaakt voordat de gegevens van het account werden verwijderd, en terugzetten maakt dat verwijderen ongedaan voor zover een kopie dat kan: wat het account sindsdien heeft toegevoegd, wordt vervangen, en koppelingen, delen, verzorgerstoegang en deellinks komen niet terug."
},
"danger-zone": {
"title": "Gevarenzone",
+4 -2
View File
@@ -7655,7 +7655,8 @@
"deleteAccountCardDetailGrace": "Twoje konto zostaje od razu zablokowane i usunięte po {days} dniach. Do tego czasu administrator tego serwera może cofnąć usunięcie.",
"deleteAccountConfirmDescriptionGrace": "Zostaniesz wylogowany, a twoje konto od razu zablokowane. Po {days} dniach zostanie trwale usunięte, razem ze wszystkimi danymi zdrowotnymi, kluczami dostępu, sesjami i wpisami audytu. Do tego czasu administrator tego serwera może cofnąć usunięcie. Kopie w kopiach zapasowych tego serwera wygasają po upływie okresu przechowywania.",
"deleteAccountScheduled": "Twoje konto jest zablokowane i zostanie usunięte {date}. Trwa wylogowywanie.",
"dangerZoneSuccessGrace": "Twoja dokumentacja zdrowotna została usunięta. Administrator tego serwera może ją przywrócić do {date}."
"dangerZoneSuccessGrace": "Twoja dokumentacja zdrowotna została usunięta. Administrator tego serwera może ją przywrócić do {date}.",
"dangerZoneRestoreLimits": "Kończy się też dostęp: to, co udostępniasz innym, to, co inni udostępniają tobie, i każda rola osoby opiekującej się. Przywrócenie odtwarza dokumentację taką, jaka jest teraz. To, co dojdzie po usunięciu, zostaje zastąpione, a połączenia integracji, udostępnienia, role opiekunów i linki udostępniania nie wracają; trzeba je skonfigurować od nowa."
},
"admin": {
"title": "Administracja",
@@ -8352,7 +8353,8 @@
"offhostFailedTitle": "Najnowszy zrzut bazy danych nie trafił do zewnętrznego bucketu",
"offhostLastUpload": "Najnowszy zrzut w buckecie pochodzi z {when}.",
"offhostNeverUploaded": "Do bucketu nie trafił jeszcze żaden zrzut bazy danych."
}
},
"restorePreWipeLimits": "Tę kopię wykonano przed usunięciem danych konta, a jej przywrócenie cofa to usunięcie na tyle, na ile pozwala kopia: to, co konto dodało od tego czasu, zostaje zastąpione, a połączenia integracji, udostępnienia, dostęp opiekunów i linki udostępniania nie wracają."
},
"danger-zone": {
"title": "Strefa zagrożenia",
+4 -2
View File
@@ -7655,7 +7655,8 @@
"deleteAccountCardDetailGrace": "Sua conta é bloqueada na hora e apagada depois de {days} dias. Até lá, um administrador deste servidor pode desfazer a exclusão.",
"deleteAccountConfirmDescriptionGrace": "Você sai da sessão e sua conta é bloqueada na hora. Depois de {days} dias, ela é apagada de vez, com todos os dados de saúde, chaves de acesso, sessões e registros de auditoria. Até lá, um administrador deste servidor pode desfazer a exclusão. As cópias nos backups deste servidor expiram depois do período de retenção.",
"deleteAccountScheduled": "Sua conta está bloqueada e será apagada em {date}. Saindo da sessão.",
"dangerZoneSuccessGrace": "Seu registro de saúde foi excluído. Um administrador deste servidor pode restaurá-lo até {date}."
"dangerZoneSuccessGrace": "Seu registro de saúde foi excluído. Um administrador deste servidor pode restaurá-lo até {date}.",
"dangerZoneRestoreLimits": "O acesso também termina: o que você compartilha com outras pessoas, o que elas compartilham com você e qualquer papel de guardião. Uma restauração traz o registro de volta como está agora. O que for adicionado depois da exclusão é substituído, e conexões de integração, compartilhamentos, papéis de guardião e links de compartilhamento não voltam; precisam ser configurados de novo."
},
"admin": {
"title": "Administração",
@@ -8352,7 +8353,8 @@
"offhostFailedTitle": "O dump mais recente do banco de dados não chegou ao bucket externo",
"offhostLastUpload": "O dump mais recente no bucket é de {when}.",
"offhostNeverUploaded": "Nenhum dump do banco de dados chegou ao bucket ainda."
}
},
"restorePreWipeLimits": "Esta cópia foi feita antes de os dados da conta serem excluídos, e restaurá-la desfaz essa exclusão até onde uma cópia consegue: o que a conta adicionou desde então é substituído, e conexões de integração, compartilhamentos, acesso de guardiões e links de compartilhamento não voltam."
},
"danger-zone": {
"title": "Zona de perigo",
@@ -0,0 +1,35 @@
/**
* Undoing a data wipe with the copy it took brings the record back as it was
* at the wipe, and nothing else: whatever arrived since is replaced, and the
* connections, sharing, Guardian access and share links the wipe ended stay
* ended. Both dialogs that lead there say so: the person's own confirmation
* before the wipe, and the admin's before the restore of such a copy.
*/
import { describe, expect, it } from "vitest";
import { restoreDescription } from "../restore-dialog";
import { wipeConfirmBody } from "@/components/settings/advanced-section";
const t = (key: string) => key;
describe("what an undo of a data wipe does not bring back", () => {
it("is said before an admin restores a copy a data wipe took, and only then", () => {
expect(
restoreDescription(
{ username: "u", type: "PRE_WIPE_1761300000000" },
t,
"when",
),
).toContain("admin.section.backups.restorePreWipeLimits");
expect(
restoreDescription({ username: "u", type: "WEEKLY_AUTO" }, t, "when"),
).not.toContain("restorePreWipeLimits");
});
it("is said before the person deletes their data, when a copy is kept", () => {
expect(wipeConfirmBody(14, t)).toContain(
"settings.dangerZoneRestoreLimits",
);
expect(wipeConfirmBody(0, t)).not.toContain("dangerZoneRestoreLimits");
});
});
@@ -20,6 +20,7 @@ import { Label } from "@/components/ui/label";
import { ApiError, apiGet } from "@/lib/api/api-fetch";
import { useFormatters, useTranslations } from "@/lib/i18n/context";
import { queryKeys } from "@/lib/query-keys";
import { isPreWipeBackupType } from "@/lib/account-deletion/pre-wipe-type";
import type { BackupRow } from "@/types/backups";
/**
@@ -221,6 +222,24 @@ export function RestorePreview({
* Mirrors the wipe dialog's pattern but adds the typed gate because the
* blast radius is bigger (re-creates rows, not just deletes).
*/
/**
* What the restore does, and for a copy a data wipe took, what undoing the
* wipe with it does not bring back.
*/
export function restoreDescription(
row: Pick<BackupRow, "username" | "type">,
t: ReturnType<typeof useTranslations>["t"],
when: string,
): string {
const replaces = t("admin.section.backups.restoreDescription", {
username: row.username,
when,
});
return isPreWipeBackupType(row.type)
? `${replaces} ${t("admin.section.backups.restorePreWipeLimits")}`
: replaces;
}
export function RestoreRowDialog({
row,
pending,
@@ -277,10 +296,7 @@ export function RestoreRowDialog({
{t("admin.section.backups.restoreTitle")}
</AlertDialogTitle>
<AlertDialogDescription>
{t("admin.section.backups.restoreDescription", {
username: row.username,
when: fmt.dateTime(row.createdAt),
})}
{restoreDescription(row, t, fmt.dateTime(row.createdAt))}
</AlertDialogDescription>
</AlertDialogHeader>
{/* What the file contains, before the typed gate. */}
+16 -7
View File
@@ -38,6 +38,21 @@ import { formatDate } from "@/lib/format";
* acknowledgment dialog, the endpoint behind it and the three user columns
* it stamped are gone. This page is the destructive controls.
*/
/**
* What deleting all data does. With a grace window it also says what a
* restore of the copy does not bring back: what arrives after the wipe is
* replaced, and connections, sharing, Guardian roles and share links are
* gone for good.
*/
export function wipeConfirmBody(
graceDays: number,
t: ReturnType<typeof useTranslations>["t"],
): string {
return graceDays > 0
? `${t("settings.dangerZoneConfirmDescriptionGrace", { days: graceDays })} ${t("settings.dangerZoneRestoreLimits")}`
: t("settings.dangerZoneConfirmDescription");
}
export function AdvancedSection() {
// v1.18.6 — the visible heading + subtitle now come from the shared
// `<SettingsSectionFrame>` in the route; this body is the advanced cards.
@@ -165,13 +180,7 @@ function DataResetCard() {
setConfirmOpen(open);
}}
title={t("settings.dangerZoneConfirm")}
body={
graceDays > 0
? t("settings.dangerZoneConfirmDescriptionGrace", {
days: graceDays,
})
: t("settings.dangerZoneConfirmDescription")
}
body={wipeConfirmBody(graceDays, t)}
confirmLabel={t("settings.finalDelete")}
onConfirm={() => void handleDeleteAllData()}
pending={deleting}