- The local database could not be opened. This can happen after a browser update or when
- storage is full.
-
-
- {message}
-
-
-
-
-
-
- Warning: resetting the database will delete all local projects and settings.
-
-
- );
-}
-
// QNBS-v3: named (not an anonymous IIFE) so a locked-storage catch below can retry the full boot
// sequence after the user unlocks, without a page reload — a reload would lose the in-memory key.
async function bootApp(): Promise {
const initResult = await initializeStorage();
if (!initResult.success) {
logger.error('StorageBackend: initializeStorage failed:', initResult.error);
- root.render(
-
- {
- await resetAllDatabases();
- window.location.reload();
- }}
- />
- ,
- );
+ await renderStorageInitializationFailure(root);
return;
}
@@ -285,19 +204,7 @@ async function bootApp(): Promise {
);
return;
}
- logger.error('Failed to initialize the application:', error);
- const msg = error instanceof Error ? error.message : 'Could not load project data.';
- root.render(
-
- {
- await resetAllDatabases();
- window.location.reload();
- }}
- />
- ,
- );
+ await renderProjectInitializationFailure(root, error);
}
}
diff --git a/locales/ar/common.json b/locales/ar/common.json
index 1e510e033..3658decde 100644
--- a/locales/ar/common.json
+++ b/locales/ar/common.json
@@ -695,5 +695,17 @@
"voice.stopDictation": "إيقاف الإملاء",
"voice.stopListening": "إيقاف الاستماع",
"worlds.emptyState.description": "ابنِ الأماكن والقواعد والتواريخ التي تعيش فيها قصتك. ابدأ بموقع.",
- "worlds.emptyState.title": "العالم بانتظارك"
+ "worlds.emptyState.title": "العالم بانتظارك",
+ "error.startup.description": "The local project or database could not be opened. Reload and try again.",
+ "error.startup.storageUnavailable": "Local storage could not be opened. Reload and try again.",
+ "error.startup.projectUnavailable": "A local project could not be opened. Reload and try again.",
+ "error.startup.reload": "Reload",
+ "error.startup.recover": "Quarantine project and reload",
+ "error.startup.recovering": "Preserving project…",
+ "error.startup.reset": "Reset database and reload",
+ "error.startup.quarantineNotice": "The complete project folder will be moved to quarantine. No project data will be deleted.",
+ "error.startup.recoveryFailed": "Project preservation failed. The original project was not deleted. Reload and try again.",
+ "error.startup.recoveryUnknown": "Project preservation could not be confirmed. Reload and try again.",
+ "error.startup.recoveryAlreadyPreserved": "The project appears to have been preserved by another recovery attempt. Reload to continue.",
+ "error.startup.resetWarning": "Resetting the database will delete all local projects and settings."
}
diff --git a/locales/de/common.json b/locales/de/common.json
index c30ccfb00..3331dbbfa 100644
--- a/locales/de/common.json
+++ b/locales/de/common.json
@@ -695,5 +695,17 @@
"voice.stopDictation": "Diktat stoppen",
"voice.stopListening": "Zuhören stoppen",
"worlds.emptyState.description": "Baue die Orte, Regeln und Geschichten auf, in denen deine Geschichte lebt. Beginne mit einem Ort.",
- "worlds.emptyState.title": "Die Welt wartet"
+ "worlds.emptyState.title": "Die Welt wartet",
+ "error.startup.description": "Das lokale Projekt oder die lokale Datenbank konnte nicht geöffnet werden. Laden Sie die Anwendung neu und versuchen Sie es erneut.",
+ "error.startup.storageUnavailable": "Der lokale Speicher konnte nicht geöffnet werden. Laden Sie die Anwendung neu und versuchen Sie es erneut.",
+ "error.startup.projectUnavailable": "Ein lokales Projekt konnte nicht geöffnet werden. Laden Sie die Anwendung neu und versuchen Sie es erneut.",
+ "error.startup.reload": "Neu laden",
+ "error.startup.recover": "Projekt unter Quarantäne stellen und neu laden",
+ "error.startup.recovering": "Projekt wird gesichert …",
+ "error.startup.reset": "Datenbank zurücksetzen und neu laden",
+ "error.startup.quarantineNotice": "Der vollständige Projektordner wird in die Quarantäne verschoben. Es werden keine Projektdaten gelöscht.",
+ "error.startup.recoveryFailed": "Die Projektsicherung ist fehlgeschlagen. Das ursprüngliche Projekt wurde nicht gelöscht. Laden Sie die Anwendung neu und versuchen Sie es erneut.",
+ "error.startup.recoveryUnknown": "Die Aufbewahrung des Projekts konnte nicht bestätigt werden. Laden Sie die Anwendung neu und versuchen Sie es erneut.",
+ "error.startup.recoveryAlreadyPreserved": "Das Projekt wurde offenbar bereits durch einen anderen Wiederherstellungsversuch gesichert. Laden Sie die Anwendung neu, um fortzufahren.",
+ "error.startup.resetWarning": "Das Zurücksetzen der Datenbank löscht alle lokalen Projekte und Einstellungen."
}
diff --git a/locales/el/common.json b/locales/el/common.json
index e05d539b2..e5dbcb634 100644
--- a/locales/el/common.json
+++ b/locales/el/common.json
@@ -695,5 +695,17 @@
"voice.stopDictation": "Σταματήστε την υπαγόρευση",
"voice.stopListening": "Σταμάτα να ακούς",
"worlds.emptyState.description": "Δημιουργήστε τα μέρη, τους κανόνες και τις ιστορίες στα οποία ζει η ιστορία σας. Ξεκινήστε με μια τοποθεσία.",
- "worlds.emptyState.title": "Ο κόσμος περιμένει"
+ "worlds.emptyState.title": "Ο κόσμος περιμένει",
+ "error.startup.description": "The local project or database could not be opened. Reload and try again.",
+ "error.startup.storageUnavailable": "Local storage could not be opened. Reload and try again.",
+ "error.startup.projectUnavailable": "A local project could not be opened. Reload and try again.",
+ "error.startup.reload": "Reload",
+ "error.startup.recover": "Quarantine project and reload",
+ "error.startup.recovering": "Preserving project…",
+ "error.startup.reset": "Reset database and reload",
+ "error.startup.quarantineNotice": "The complete project folder will be moved to quarantine. No project data will be deleted.",
+ "error.startup.recoveryFailed": "Project preservation failed. The original project was not deleted. Reload and try again.",
+ "error.startup.recoveryUnknown": "Project preservation could not be confirmed. Reload and try again.",
+ "error.startup.recoveryAlreadyPreserved": "The project appears to have been preserved by another recovery attempt. Reload to continue.",
+ "error.startup.resetWarning": "Resetting the database will delete all local projects and settings."
}
diff --git a/locales/en/common.json b/locales/en/common.json
index 0e0bf3776..9494cd404 100644
--- a/locales/en/common.json
+++ b/locales/en/common.json
@@ -695,5 +695,17 @@
"plotBoard.heuristic.complicate.rationale": "A fresh complication tests the characters and opens new threads.",
"plotBoard.heuristic.reverse.title": "Add a reversal",
"plotBoard.heuristic.reverse.description": "Flip an expectation — an ally falters, a win turns sour, or a hidden truth surfaces.",
- "plotBoard.heuristic.reverse.rationale": "Reversals refresh tension and deepen the arc."
+ "plotBoard.heuristic.reverse.rationale": "Reversals refresh tension and deepen the arc.",
+ "error.startup.description": "The local project or database could not be opened. Reload and try again.",
+ "error.startup.storageUnavailable": "Local storage could not be opened. Reload and try again.",
+ "error.startup.projectUnavailable": "A local project could not be opened. Reload and try again.",
+ "error.startup.reload": "Reload",
+ "error.startup.recover": "Quarantine project and reload",
+ "error.startup.recovering": "Preserving project…",
+ "error.startup.reset": "Reset database and reload",
+ "error.startup.quarantineNotice": "The complete project folder will be moved to quarantine. No project data will be deleted.",
+ "error.startup.recoveryFailed": "Project preservation failed. The original project was not deleted. Reload and try again.",
+ "error.startup.recoveryUnknown": "Project preservation could not be confirmed. Reload and try again.",
+ "error.startup.recoveryAlreadyPreserved": "The project appears to have been preserved by another recovery attempt. Reload to continue.",
+ "error.startup.resetWarning": "Resetting the database will delete all local projects and settings."
}
diff --git a/locales/es/common.json b/locales/es/common.json
index 71511fc46..41117663e 100644
--- a/locales/es/common.json
+++ b/locales/es/common.json
@@ -695,5 +695,17 @@
"voice.stopDictation": "Detener dictado",
"voice.stopListening": "Detener escucha",
"worlds.emptyState.description": "Construye los lugares, reglas e historias en los que vive tu historia. Comienza con una ubicación.",
- "worlds.emptyState.title": "El mundo te espera"
+ "worlds.emptyState.title": "El mundo te espera",
+ "error.startup.description": "No se pudo abrir el proyecto local o la base de datos. Recarga la aplicación e inténtalo de nuevo.",
+ "error.startup.storageUnavailable": "No se pudo abrir el almacenamiento local. Recarga la aplicación e inténtalo de nuevo.",
+ "error.startup.projectUnavailable": "No se pudo abrir un proyecto local. Recarga la aplicación e inténtalo de nuevo.",
+ "error.startup.reload": "Recargar",
+ "error.startup.recover": "Poner el proyecto en cuarentena y recargar",
+ "error.startup.recovering": "Preservando el proyecto…",
+ "error.startup.reset": "Restablecer la base de datos y recargar",
+ "error.startup.quarantineNotice": "La carpeta completa del proyecto se moverá a la cuarentena. No se eliminarán datos del proyecto.",
+ "error.startup.recoveryFailed": "La preservación del proyecto falló. El proyecto original no se eliminó. Recarga la aplicación e inténtalo de nuevo.",
+ "error.startup.recoveryUnknown": "No se pudo confirmar la preservación del proyecto. Recarga la aplicación e inténtalo de nuevo.",
+ "error.startup.recoveryAlreadyPreserved": "Parece que otro intento de recuperación ya ha preservado el proyecto. Recarga la aplicación para continuar.",
+ "error.startup.resetWarning": "Restablecer la base de datos eliminará todos los proyectos y la configuración locales."
}
diff --git a/locales/eu/common.json b/locales/eu/common.json
index 11683111b..921244f15 100644
--- a/locales/eu/common.json
+++ b/locales/eu/common.json
@@ -695,5 +695,17 @@
"voice.stopDictation": "Utzi diktaketari",
"voice.stopListening": "Utzi entzuteari",
"worlds.emptyState.description": "Eraiki zure istorioa bizi den lekuak, arauak eta historiak. Hasi kokapen batekin.",
- "worlds.emptyState.title": "Mundua zain dago"
+ "worlds.emptyState.title": "Mundua zain dago",
+ "error.startup.description": "The local project or database could not be opened. Reload and try again.",
+ "error.startup.storageUnavailable": "Local storage could not be opened. Reload and try again.",
+ "error.startup.projectUnavailable": "A local project could not be opened. Reload and try again.",
+ "error.startup.reload": "Reload",
+ "error.startup.recover": "Quarantine project and reload",
+ "error.startup.recovering": "Preserving project…",
+ "error.startup.reset": "Reset database and reload",
+ "error.startup.quarantineNotice": "The complete project folder will be moved to quarantine. No project data will be deleted.",
+ "error.startup.recoveryFailed": "Project preservation failed. The original project was not deleted. Reload and try again.",
+ "error.startup.recoveryUnknown": "Project preservation could not be confirmed. Reload and try again.",
+ "error.startup.recoveryAlreadyPreserved": "The project appears to have been preserved by another recovery attempt. Reload to continue.",
+ "error.startup.resetWarning": "Resetting the database will delete all local projects and settings."
}
diff --git a/locales/fa/common.json b/locales/fa/common.json
index 28dd9bf19..775617ef7 100644
--- a/locales/fa/common.json
+++ b/locales/fa/common.json
@@ -695,5 +695,17 @@
"voice.stopDictation": "دیکته را متوقف کنید",
"voice.stopListening": "گوش دادن را متوقف کنید",
"worlds.emptyState.description": "مکانها، قوانین و تاریخهایی را بسازید که داستانتان در آن زندگی میکند. با یک مکان شروع کنید.",
- "worlds.emptyState.title": "دنیا منتظر است"
+ "worlds.emptyState.title": "دنیا منتظر است",
+ "error.startup.description": "The local project or database could not be opened. Reload and try again.",
+ "error.startup.storageUnavailable": "Local storage could not be opened. Reload and try again.",
+ "error.startup.projectUnavailable": "A local project could not be opened. Reload and try again.",
+ "error.startup.reload": "Reload",
+ "error.startup.recover": "Quarantine project and reload",
+ "error.startup.recovering": "Preserving project…",
+ "error.startup.reset": "Reset database and reload",
+ "error.startup.quarantineNotice": "The complete project folder will be moved to quarantine. No project data will be deleted.",
+ "error.startup.recoveryFailed": "Project preservation failed. The original project was not deleted. Reload and try again.",
+ "error.startup.recoveryUnknown": "Project preservation could not be confirmed. Reload and try again.",
+ "error.startup.recoveryAlreadyPreserved": "The project appears to have been preserved by another recovery attempt. Reload to continue.",
+ "error.startup.resetWarning": "Resetting the database will delete all local projects and settings."
}
diff --git a/locales/fi/common.json b/locales/fi/common.json
index 2f9e73e25..bdd59edca 100644
--- a/locales/fi/common.json
+++ b/locales/fi/common.json
@@ -695,5 +695,17 @@
"voice.stopDictation": "Lopeta sanelu",
"voice.stopListening": "Lopeta kuunteleminen",
"worlds.emptyState.description": "Rakenna paikat, säännöt ja historiat, joissa tarinasi elää. Aloita sijainnista.",
- "worlds.emptyState.title": "Maailma odottaa"
+ "worlds.emptyState.title": "Maailma odottaa",
+ "error.startup.description": "The local project or database could not be opened. Reload and try again.",
+ "error.startup.storageUnavailable": "Local storage could not be opened. Reload and try again.",
+ "error.startup.projectUnavailable": "A local project could not be opened. Reload and try again.",
+ "error.startup.reload": "Reload",
+ "error.startup.recover": "Quarantine project and reload",
+ "error.startup.recovering": "Preserving project…",
+ "error.startup.reset": "Reset database and reload",
+ "error.startup.quarantineNotice": "The complete project folder will be moved to quarantine. No project data will be deleted.",
+ "error.startup.recoveryFailed": "Project preservation failed. The original project was not deleted. Reload and try again.",
+ "error.startup.recoveryUnknown": "Project preservation could not be confirmed. Reload and try again.",
+ "error.startup.recoveryAlreadyPreserved": "The project appears to have been preserved by another recovery attempt. Reload to continue.",
+ "error.startup.resetWarning": "Resetting the database will delete all local projects and settings."
}
diff --git a/locales/fr/common.json b/locales/fr/common.json
index 5710d2efc..1c0fc7cd9 100644
--- a/locales/fr/common.json
+++ b/locales/fr/common.json
@@ -695,5 +695,17 @@
"voice.stopDictation": "Arrêter la dictée",
"voice.stopListening": "Arrêter l’écoute",
"worlds.emptyState.description": "Construisez les lieux, les règles et les histoires dans lesquels votre récit prend vie. Commencez par un lieu.",
- "worlds.emptyState.title": "Le monde vous attend"
+ "worlds.emptyState.title": "Le monde vous attend",
+ "error.startup.description": "Le projet local ou la base de données n’a pas pu être ouvert. Rechargez l’application et réessayez.",
+ "error.startup.storageUnavailable": "Le stockage local n’a pas pu être ouvert. Rechargez l’application et réessayez.",
+ "error.startup.projectUnavailable": "Un projet local n’a pas pu être ouvert. Rechargez l’application et réessayez.",
+ "error.startup.reload": "Recharger",
+ "error.startup.recover": "Mettre le projet en quarantaine et recharger",
+ "error.startup.recovering": "Préservation du projet…",
+ "error.startup.reset": "Réinitialiser la base de données et recharger",
+ "error.startup.quarantineNotice": "Le dossier complet du projet sera déplacé en quarantaine. Aucune donnée du projet ne sera supprimée.",
+ "error.startup.recoveryFailed": "La préservation du projet a échoué. Le projet d’origine n’a pas été supprimé. Rechargez l’application et réessayez.",
+ "error.startup.recoveryUnknown": "La préservation du projet n’a pas pu être confirmée. Rechargez l’application et réessayez.",
+ "error.startup.recoveryAlreadyPreserved": "Le projet semble avoir été préservé par une autre tentative de récupération. Rechargez l’application pour continuer.",
+ "error.startup.resetWarning": "La réinitialisation de la base de données supprimera tous les projets et réglages locaux."
}
diff --git a/locales/he/common.json b/locales/he/common.json
index ad004c4b2..c0fce188c 100644
--- a/locales/he/common.json
+++ b/locales/he/common.json
@@ -695,5 +695,17 @@
"voice.stopDictation": "עצירת הכתבה",
"voice.stopListening": "עצירת האזנה",
"worlds.emptyState.description": "בנו את המקומות, החוקים וההיסטוריות שבהם הסיפור שלכם חי. התחילו במיקום.",
- "worlds.emptyState.title": "העולם ממתין"
+ "worlds.emptyState.title": "העולם ממתין",
+ "error.startup.description": "The local project or database could not be opened. Reload and try again.",
+ "error.startup.storageUnavailable": "Local storage could not be opened. Reload and try again.",
+ "error.startup.projectUnavailable": "A local project could not be opened. Reload and try again.",
+ "error.startup.reload": "Reload",
+ "error.startup.recover": "Quarantine project and reload",
+ "error.startup.recovering": "Preserving project…",
+ "error.startup.reset": "Reset database and reload",
+ "error.startup.quarantineNotice": "The complete project folder will be moved to quarantine. No project data will be deleted.",
+ "error.startup.recoveryFailed": "Project preservation failed. The original project was not deleted. Reload and try again.",
+ "error.startup.recoveryUnknown": "Project preservation could not be confirmed. Reload and try again.",
+ "error.startup.recoveryAlreadyPreserved": "The project appears to have been preserved by another recovery attempt. Reload to continue.",
+ "error.startup.resetWarning": "Resetting the database will delete all local projects and settings."
}
diff --git a/locales/hu/common.json b/locales/hu/common.json
index 72c3b23d6..3519d768f 100644
--- a/locales/hu/common.json
+++ b/locales/hu/common.json
@@ -695,5 +695,17 @@
"voice.stopDictation": "Hagyd abba a diktálást",
"voice.stopListening": "Ne hallgasson",
"worlds.emptyState.description": "Építsd fel azokat a helyeket, szabályokat és történeteket, amelyekben a történeted él. Kezdd egy hellyel.",
- "worlds.emptyState.title": "A világ vár"
+ "worlds.emptyState.title": "A világ vár",
+ "error.startup.description": "The local project or database could not be opened. Reload and try again.",
+ "error.startup.storageUnavailable": "Local storage could not be opened. Reload and try again.",
+ "error.startup.projectUnavailable": "A local project could not be opened. Reload and try again.",
+ "error.startup.reload": "Reload",
+ "error.startup.recover": "Quarantine project and reload",
+ "error.startup.recovering": "Preserving project…",
+ "error.startup.reset": "Reset database and reload",
+ "error.startup.quarantineNotice": "The complete project folder will be moved to quarantine. No project data will be deleted.",
+ "error.startup.recoveryFailed": "Project preservation failed. The original project was not deleted. Reload and try again.",
+ "error.startup.recoveryUnknown": "Project preservation could not be confirmed. Reload and try again.",
+ "error.startup.recoveryAlreadyPreserved": "The project appears to have been preserved by another recovery attempt. Reload to continue.",
+ "error.startup.resetWarning": "Resetting the database will delete all local projects and settings."
}
diff --git a/locales/is/common.json b/locales/is/common.json
index 95b86efd3..c8925c403 100644
--- a/locales/is/common.json
+++ b/locales/is/common.json
@@ -695,5 +695,17 @@
"voice.stopDictation": "Hættu einræði",
"voice.stopListening": "Hættu að hlusta",
"worlds.emptyState.description": "Búðu til staðina, reglurnar og söguna sem sagan þín býr í. Byrjaðu á staðsetningu.",
- "worlds.emptyState.title": "Heimurinn bíður"
+ "worlds.emptyState.title": "Heimurinn bíður",
+ "error.startup.description": "The local project or database could not be opened. Reload and try again.",
+ "error.startup.storageUnavailable": "Local storage could not be opened. Reload and try again.",
+ "error.startup.projectUnavailable": "A local project could not be opened. Reload and try again.",
+ "error.startup.reload": "Reload",
+ "error.startup.recover": "Quarantine project and reload",
+ "error.startup.recovering": "Preserving project…",
+ "error.startup.reset": "Reset database and reload",
+ "error.startup.quarantineNotice": "The complete project folder will be moved to quarantine. No project data will be deleted.",
+ "error.startup.recoveryFailed": "Project preservation failed. The original project was not deleted. Reload and try again.",
+ "error.startup.recoveryUnknown": "Project preservation could not be confirmed. Reload and try again.",
+ "error.startup.recoveryAlreadyPreserved": "The project appears to have been preserved by another recovery attempt. Reload to continue.",
+ "error.startup.resetWarning": "Resetting the database will delete all local projects and settings."
}
diff --git a/locales/it/common.json b/locales/it/common.json
index cfa715895..a4197f98b 100644
--- a/locales/it/common.json
+++ b/locales/it/common.json
@@ -695,5 +695,17 @@
"voice.stopDictation": "Ferma dettatura",
"voice.stopListening": "Ferma ascolto",
"worlds.emptyState.description": "Costruisci i luoghi, le regole e le storie in cui vive la tua narrazione. Inizia con una location.",
- "worlds.emptyState.title": "Il mondo ti aspetta"
+ "worlds.emptyState.title": "Il mondo ti aspetta",
+ "error.startup.description": "Non è stato possibile aprire il progetto locale o il database. Ricarica l’applicazione e riprova.",
+ "error.startup.storageUnavailable": "Non è stato possibile aprire l’archiviazione locale. Ricarica l’applicazione e riprova.",
+ "error.startup.projectUnavailable": "Non è stato possibile aprire un progetto locale. Ricarica l’applicazione e riprova.",
+ "error.startup.reload": "Ricarica",
+ "error.startup.recover": "Metti il progetto in quarantena e ricarica",
+ "error.startup.recovering": "Conservazione del progetto…",
+ "error.startup.reset": "Reimposta il database e ricarica",
+ "error.startup.quarantineNotice": "La cartella completa del progetto verrà spostata in quarantena. Nessun dato del progetto verrà eliminato.",
+ "error.startup.recoveryFailed": "La conservazione del progetto non è riuscita. Il progetto originale non è stato eliminato. Ricarica l’applicazione e riprova.",
+ "error.startup.recoveryUnknown": "Non è stato possibile confermare la conservazione del progetto. Ricarica l’applicazione e riprova.",
+ "error.startup.recoveryAlreadyPreserved": "Sembra che un altro tentativo di recupero abbia già conservato il progetto. Ricarica per continuare.",
+ "error.startup.resetWarning": "La reimpostazione del database eliminerà tutti i progetti e le impostazioni locali."
}
diff --git a/locales/ja/common.json b/locales/ja/common.json
index 65af5ab70..3a39a6f95 100644
--- a/locales/ja/common.json
+++ b/locales/ja/common.json
@@ -695,5 +695,17 @@
"voice.stopDictation": "ディクテーションを停止する",
"voice.stopListening": "聞くのをやめる",
"worlds.emptyState.description": "あなたの物語が生きる場所、ルール、歴史を構築します。場所から始めます。",
- "worlds.emptyState.title": "世界が待っています"
+ "worlds.emptyState.title": "世界が待っています",
+ "error.startup.description": "The local project or database could not be opened. Reload and try again.",
+ "error.startup.storageUnavailable": "Local storage could not be opened. Reload and try again.",
+ "error.startup.projectUnavailable": "A local project could not be opened. Reload and try again.",
+ "error.startup.reload": "Reload",
+ "error.startup.recover": "Quarantine project and reload",
+ "error.startup.recovering": "Preserving project…",
+ "error.startup.reset": "Reset database and reload",
+ "error.startup.quarantineNotice": "The complete project folder will be moved to quarantine. No project data will be deleted.",
+ "error.startup.recoveryFailed": "Project preservation failed. The original project was not deleted. Reload and try again.",
+ "error.startup.recoveryUnknown": "Project preservation could not be confirmed. Reload and try again.",
+ "error.startup.recoveryAlreadyPreserved": "The project appears to have been preserved by another recovery attempt. Reload to continue.",
+ "error.startup.resetWarning": "Resetting the database will delete all local projects and settings."
}
diff --git a/locales/ko/common.json b/locales/ko/common.json
index c35d294bb..3ae366b81 100644
--- a/locales/ko/common.json
+++ b/locales/ko/common.json
@@ -695,5 +695,17 @@
"voice.stopDictation": "받아쓰기 중지",
"voice.stopListening": "듣기 중지",
"worlds.emptyState.description": "당신의 이야기가 담긴 장소, 규칙, 역사를 만들어 보세요. 위치부터 시작하세요.",
- "worlds.emptyState.title": "세계가 기다리고 있다"
+ "worlds.emptyState.title": "세계가 기다리고 있다",
+ "error.startup.description": "The local project or database could not be opened. Reload and try again.",
+ "error.startup.storageUnavailable": "Local storage could not be opened. Reload and try again.",
+ "error.startup.projectUnavailable": "A local project could not be opened. Reload and try again.",
+ "error.startup.reload": "Reload",
+ "error.startup.recover": "Quarantine project and reload",
+ "error.startup.recovering": "Preserving project…",
+ "error.startup.reset": "Reset database and reload",
+ "error.startup.quarantineNotice": "The complete project folder will be moved to quarantine. No project data will be deleted.",
+ "error.startup.recoveryFailed": "Project preservation failed. The original project was not deleted. Reload and try again.",
+ "error.startup.recoveryUnknown": "Project preservation could not be confirmed. Reload and try again.",
+ "error.startup.recoveryAlreadyPreserved": "The project appears to have been preserved by another recovery attempt. Reload to continue.",
+ "error.startup.resetWarning": "Resetting the database will delete all local projects and settings."
}
diff --git a/locales/pt/common.json b/locales/pt/common.json
index 8e9994a3c..9e5a0d511 100644
--- a/locales/pt/common.json
+++ b/locales/pt/common.json
@@ -695,5 +695,17 @@
"voice.stopDictation": "Pare o ditado",
"voice.stopListening": "Pare de ouvir",
"worlds.emptyState.description": "Construa os lugares, regras e histórias em que sua história vive. Comece com um local.",
- "worlds.emptyState.title": "O mundo espera"
+ "worlds.emptyState.title": "O mundo espera",
+ "error.startup.description": "The local project or database could not be opened. Reload and try again.",
+ "error.startup.storageUnavailable": "Local storage could not be opened. Reload and try again.",
+ "error.startup.projectUnavailable": "A local project could not be opened. Reload and try again.",
+ "error.startup.reload": "Reload",
+ "error.startup.recover": "Quarantine project and reload",
+ "error.startup.recovering": "Preserving project…",
+ "error.startup.reset": "Reset database and reload",
+ "error.startup.quarantineNotice": "The complete project folder will be moved to quarantine. No project data will be deleted.",
+ "error.startup.recoveryFailed": "Project preservation failed. The original project was not deleted. Reload and try again.",
+ "error.startup.recoveryUnknown": "Project preservation could not be confirmed. Reload and try again.",
+ "error.startup.recoveryAlreadyPreserved": "The project appears to have been preserved by another recovery attempt. Reload to continue.",
+ "error.startup.resetWarning": "Resetting the database will delete all local projects and settings."
}
diff --git a/locales/ru/common.json b/locales/ru/common.json
index adbed0250..25ea3864c 100644
--- a/locales/ru/common.json
+++ b/locales/ru/common.json
@@ -695,5 +695,17 @@
"voice.stopDictation": "Остановить диктовку",
"voice.stopListening": "Хватит слушать",
"worlds.emptyState.description": "Создайте места, правила и историю, в которых живет ваша история. Начните с локации.",
- "worlds.emptyState.title": "Мир ждет"
+ "worlds.emptyState.title": "Мир ждет",
+ "error.startup.description": "The local project or database could not be opened. Reload and try again.",
+ "error.startup.storageUnavailable": "Local storage could not be opened. Reload and try again.",
+ "error.startup.projectUnavailable": "A local project could not be opened. Reload and try again.",
+ "error.startup.reload": "Reload",
+ "error.startup.recover": "Quarantine project and reload",
+ "error.startup.recovering": "Preserving project…",
+ "error.startup.reset": "Reset database and reload",
+ "error.startup.quarantineNotice": "The complete project folder will be moved to quarantine. No project data will be deleted.",
+ "error.startup.recoveryFailed": "Project preservation failed. The original project was not deleted. Reload and try again.",
+ "error.startup.recoveryUnknown": "Project preservation could not be confirmed. Reload and try again.",
+ "error.startup.recoveryAlreadyPreserved": "The project appears to have been preserved by another recovery attempt. Reload to continue.",
+ "error.startup.resetWarning": "Resetting the database will delete all local projects and settings."
}
diff --git a/locales/sv/common.json b/locales/sv/common.json
index 7e82b08fa..fa5a3ddf4 100644
--- a/locales/sv/common.json
+++ b/locales/sv/common.json
@@ -695,5 +695,17 @@
"voice.stopDictation": "Sluta diktera",
"voice.stopListening": "Sluta lyssna",
"worlds.emptyState.description": "Bygg upp platserna, reglerna och historien som din berättelse lever i. Börja med en plats.",
- "worlds.emptyState.title": "Världen väntar"
+ "worlds.emptyState.title": "Världen väntar",
+ "error.startup.description": "The local project or database could not be opened. Reload and try again.",
+ "error.startup.storageUnavailable": "Local storage could not be opened. Reload and try again.",
+ "error.startup.projectUnavailable": "A local project could not be opened. Reload and try again.",
+ "error.startup.reload": "Reload",
+ "error.startup.recover": "Quarantine project and reload",
+ "error.startup.recovering": "Preserving project…",
+ "error.startup.reset": "Reset database and reload",
+ "error.startup.quarantineNotice": "The complete project folder will be moved to quarantine. No project data will be deleted.",
+ "error.startup.recoveryFailed": "Project preservation failed. The original project was not deleted. Reload and try again.",
+ "error.startup.recoveryUnknown": "Project preservation could not be confirmed. Reload and try again.",
+ "error.startup.recoveryAlreadyPreserved": "The project appears to have been preserved by another recovery attempt. Reload to continue.",
+ "error.startup.resetWarning": "Resetting the database will delete all local projects and settings."
}
diff --git a/locales/zh/common.json b/locales/zh/common.json
index 972777528..cca1db166 100644
--- a/locales/zh/common.json
+++ b/locales/zh/common.json
@@ -695,5 +695,17 @@
"voice.stopDictation": "停止听写",
"voice.stopListening": "停止聆听",
"worlds.emptyState.description": "构建你的故事所存在的地点、规则和历史。从一个地点开始。",
- "worlds.emptyState.title": "世界等待着"
+ "worlds.emptyState.title": "世界等待着",
+ "error.startup.description": "The local project or database could not be opened. Reload and try again.",
+ "error.startup.storageUnavailable": "Local storage could not be opened. Reload and try again.",
+ "error.startup.projectUnavailable": "A local project could not be opened. Reload and try again.",
+ "error.startup.reload": "Reload",
+ "error.startup.recover": "Quarantine project and reload",
+ "error.startup.recovering": "Preserving project…",
+ "error.startup.reset": "Reset database and reload",
+ "error.startup.quarantineNotice": "The complete project folder will be moved to quarantine. No project data will be deleted.",
+ "error.startup.recoveryFailed": "Project preservation failed. The original project was not deleted. Reload and try again.",
+ "error.startup.recoveryUnknown": "Project preservation could not be confirmed. Reload and try again.",
+ "error.startup.recoveryAlreadyPreserved": "The project appears to have been preserved by another recovery attempt. Reload to continue.",
+ "error.startup.resetWarning": "Resetting the database will delete all local projects and settings."
}
diff --git a/public/locales/ar/bundle.json b/public/locales/ar/bundle.json
index 286c1452e..faf17d4b5 100644
--- a/public/locales/ar/bundle.json
+++ b/public/locales/ar/bundle.json
@@ -812,6 +812,18 @@
"voice.stopListening": "إيقاف الاستماع",
"worlds.emptyState.description": "ابنِ الأماكن والقواعد والتواريخ التي تعيش فيها قصتك. ابدأ بموقع.",
"worlds.emptyState.title": "العالم بانتظارك",
+ "error.startup.description": "The local project or database could not be opened. Reload and try again.",
+ "error.startup.storageUnavailable": "Local storage could not be opened. Reload and try again.",
+ "error.startup.projectUnavailable": "A local project could not be opened. Reload and try again.",
+ "error.startup.reload": "Reload",
+ "error.startup.recover": "Quarantine project and reload",
+ "error.startup.recovering": "Preserving project…",
+ "error.startup.reset": "Reset database and reload",
+ "error.startup.quarantineNotice": "The complete project folder will be moved to quarantine. No project data will be deleted.",
+ "error.startup.recoveryFailed": "Project preservation failed. The original project was not deleted. Reload and try again.",
+ "error.startup.recoveryUnknown": "Project preservation could not be confirmed. Reload and try again.",
+ "error.startup.recoveryAlreadyPreserved": "The project appears to have been preserved by another recovery attempt. Reload to continue.",
+ "error.startup.resetWarning": "Resetting the database will delete all local projects and settings.",
"copilot.annotationCount": "{{count}} insight for this chapter",
"copilot.announceClosed": "AI Copilot closed",
"copilot.announceOpened": "AI Copilot opened",
diff --git a/public/locales/de/bundle.json b/public/locales/de/bundle.json
index c4e75899d..f8c2e723c 100644
--- a/public/locales/de/bundle.json
+++ b/public/locales/de/bundle.json
@@ -812,6 +812,18 @@
"voice.stopListening": "Zuhören stoppen",
"worlds.emptyState.description": "Baue die Orte, Regeln und Geschichten auf, in denen deine Geschichte lebt. Beginne mit einem Ort.",
"worlds.emptyState.title": "Die Welt wartet",
+ "error.startup.description": "Das lokale Projekt oder die lokale Datenbank konnte nicht geöffnet werden. Laden Sie die Anwendung neu und versuchen Sie es erneut.",
+ "error.startup.storageUnavailable": "Der lokale Speicher konnte nicht geöffnet werden. Laden Sie die Anwendung neu und versuchen Sie es erneut.",
+ "error.startup.projectUnavailable": "Ein lokales Projekt konnte nicht geöffnet werden. Laden Sie die Anwendung neu und versuchen Sie es erneut.",
+ "error.startup.reload": "Neu laden",
+ "error.startup.recover": "Projekt unter Quarantäne stellen und neu laden",
+ "error.startup.recovering": "Projekt wird gesichert …",
+ "error.startup.reset": "Datenbank zurücksetzen und neu laden",
+ "error.startup.quarantineNotice": "Der vollständige Projektordner wird in die Quarantäne verschoben. Es werden keine Projektdaten gelöscht.",
+ "error.startup.recoveryFailed": "Die Projektsicherung ist fehlgeschlagen. Das ursprüngliche Projekt wurde nicht gelöscht. Laden Sie die Anwendung neu und versuchen Sie es erneut.",
+ "error.startup.recoveryUnknown": "Die Aufbewahrung des Projekts konnte nicht bestätigt werden. Laden Sie die Anwendung neu und versuchen Sie es erneut.",
+ "error.startup.recoveryAlreadyPreserved": "Das Projekt wurde offenbar bereits durch einen anderen Wiederherstellungsversuch gesichert. Laden Sie die Anwendung neu, um fortzufahren.",
+ "error.startup.resetWarning": "Das Zurücksetzen der Datenbank löscht alle lokalen Projekte und Einstellungen.",
"copilot.annotationCount": "{{count}} insight for this chapter",
"copilot.announceClosed": "KI-Copilot geschlossen",
"copilot.announceOpened": "KI-Copilot geöffnet",
diff --git a/public/locales/el/bundle.json b/public/locales/el/bundle.json
index 27ef72a23..9fbad20e1 100644
--- a/public/locales/el/bundle.json
+++ b/public/locales/el/bundle.json
@@ -812,6 +812,18 @@
"voice.stopListening": "Σταμάτα να ακούς",
"worlds.emptyState.description": "Δημιουργήστε τα μέρη, τους κανόνες και τις ιστορίες στα οποία ζει η ιστορία σας. Ξεκινήστε με μια τοποθεσία.",
"worlds.emptyState.title": "Ο κόσμος περιμένει",
+ "error.startup.description": "The local project or database could not be opened. Reload and try again.",
+ "error.startup.storageUnavailable": "Local storage could not be opened. Reload and try again.",
+ "error.startup.projectUnavailable": "A local project could not be opened. Reload and try again.",
+ "error.startup.reload": "Reload",
+ "error.startup.recover": "Quarantine project and reload",
+ "error.startup.recovering": "Preserving project…",
+ "error.startup.reset": "Reset database and reload",
+ "error.startup.quarantineNotice": "The complete project folder will be moved to quarantine. No project data will be deleted.",
+ "error.startup.recoveryFailed": "Project preservation failed. The original project was not deleted. Reload and try again.",
+ "error.startup.recoveryUnknown": "Project preservation could not be confirmed. Reload and try again.",
+ "error.startup.recoveryAlreadyPreserved": "The project appears to have been preserved by another recovery attempt. Reload to continue.",
+ "error.startup.resetWarning": "Resetting the database will delete all local projects and settings.",
"copilot.annotationCount": "{{count}} πληροφορίες για αυτό το κεφάλαιο",
"copilot.announceClosed": "Το AI Copilot έκλεισε",
"copilot.announceOpened": "Άνοιξε το AI Copilot",
diff --git a/public/locales/en/bundle.json b/public/locales/en/bundle.json
index dfea22b2a..abb0be733 100644
--- a/public/locales/en/bundle.json
+++ b/public/locales/en/bundle.json
@@ -812,6 +812,18 @@
"plotBoard.heuristic.reverse.title": "Add a reversal",
"plotBoard.heuristic.reverse.description": "Flip an expectation — an ally falters, a win turns sour, or a hidden truth surfaces.",
"plotBoard.heuristic.reverse.rationale": "Reversals refresh tension and deepen the arc.",
+ "error.startup.description": "The local project or database could not be opened. Reload and try again.",
+ "error.startup.storageUnavailable": "Local storage could not be opened. Reload and try again.",
+ "error.startup.projectUnavailable": "A local project could not be opened. Reload and try again.",
+ "error.startup.reload": "Reload",
+ "error.startup.recover": "Quarantine project and reload",
+ "error.startup.recovering": "Preserving project…",
+ "error.startup.reset": "Reset database and reload",
+ "error.startup.quarantineNotice": "The complete project folder will be moved to quarantine. No project data will be deleted.",
+ "error.startup.recoveryFailed": "Project preservation failed. The original project was not deleted. Reload and try again.",
+ "error.startup.recoveryUnknown": "Project preservation could not be confirmed. Reload and try again.",
+ "error.startup.recoveryAlreadyPreserved": "The project appears to have been preserved by another recovery attempt. Reload to continue.",
+ "error.startup.resetWarning": "Resetting the database will delete all local projects and settings.",
"copilot.title": "AI Copilot",
"copilot.subtitle": "Your writing assistant",
"copilot.launcherLabel": "Open AI Copilot",
diff --git a/public/locales/es/bundle.json b/public/locales/es/bundle.json
index a9eaebea9..d7f3bba7e 100644
--- a/public/locales/es/bundle.json
+++ b/public/locales/es/bundle.json
@@ -812,6 +812,18 @@
"voice.stopListening": "Detener escucha",
"worlds.emptyState.description": "Construye los lugares, reglas e historias en los que vive tu historia. Comienza con una ubicación.",
"worlds.emptyState.title": "El mundo te espera",
+ "error.startup.description": "No se pudo abrir el proyecto local o la base de datos. Recarga la aplicación e inténtalo de nuevo.",
+ "error.startup.storageUnavailable": "No se pudo abrir el almacenamiento local. Recarga la aplicación e inténtalo de nuevo.",
+ "error.startup.projectUnavailable": "No se pudo abrir un proyecto local. Recarga la aplicación e inténtalo de nuevo.",
+ "error.startup.reload": "Recargar",
+ "error.startup.recover": "Poner el proyecto en cuarentena y recargar",
+ "error.startup.recovering": "Preservando el proyecto…",
+ "error.startup.reset": "Restablecer la base de datos y recargar",
+ "error.startup.quarantineNotice": "La carpeta completa del proyecto se moverá a la cuarentena. No se eliminarán datos del proyecto.",
+ "error.startup.recoveryFailed": "La preservación del proyecto falló. El proyecto original no se eliminó. Recarga la aplicación e inténtalo de nuevo.",
+ "error.startup.recoveryUnknown": "No se pudo confirmar la preservación del proyecto. Recarga la aplicación e inténtalo de nuevo.",
+ "error.startup.recoveryAlreadyPreserved": "Parece que otro intento de recuperación ya ha preservado el proyecto. Recarga la aplicación para continuar.",
+ "error.startup.resetWarning": "Restablecer la base de datos eliminará todos los proyectos y la configuración locales.",
"copilot.annotationCount": "{{count}} insight for this chapter",
"copilot.announceClosed": "Copiloto IA cerrado",
"copilot.announceOpened": "Copiloto IA abierto",
diff --git a/public/locales/eu/bundle.json b/public/locales/eu/bundle.json
index 7b6c5a74c..87d4676e6 100644
--- a/public/locales/eu/bundle.json
+++ b/public/locales/eu/bundle.json
@@ -812,6 +812,18 @@
"voice.stopListening": "Utzi entzuteari",
"worlds.emptyState.description": "Eraiki zure istorioa bizi den lekuak, arauak eta historiak. Hasi kokapen batekin.",
"worlds.emptyState.title": "Mundua zain dago",
+ "error.startup.description": "The local project or database could not be opened. Reload and try again.",
+ "error.startup.storageUnavailable": "Local storage could not be opened. Reload and try again.",
+ "error.startup.projectUnavailable": "A local project could not be opened. Reload and try again.",
+ "error.startup.reload": "Reload",
+ "error.startup.recover": "Quarantine project and reload",
+ "error.startup.recovering": "Preserving project…",
+ "error.startup.reset": "Reset database and reload",
+ "error.startup.quarantineNotice": "The complete project folder will be moved to quarantine. No project data will be deleted.",
+ "error.startup.recoveryFailed": "Project preservation failed. The original project was not deleted. Reload and try again.",
+ "error.startup.recoveryUnknown": "Project preservation could not be confirmed. Reload and try again.",
+ "error.startup.recoveryAlreadyPreserved": "The project appears to have been preserved by another recovery attempt. Reload to continue.",
+ "error.startup.resetWarning": "Resetting the database will delete all local projects and settings.",
"copilot.annotationCount": "{{count}} kapitulu honetarako ikuspegia",
"copilot.announceClosed": "AI Copilot itxita",
"copilot.announceOpened": "AI Copilot ireki da",
diff --git a/public/locales/fa/bundle.json b/public/locales/fa/bundle.json
index ec94a5385..b5f8d0a11 100644
--- a/public/locales/fa/bundle.json
+++ b/public/locales/fa/bundle.json
@@ -812,6 +812,18 @@
"voice.stopListening": "گوش دادن را متوقف کنید",
"worlds.emptyState.description": "مکانها، قوانین و تاریخهایی را بسازید که داستانتان در آن زندگی میکند. با یک مکان شروع کنید.",
"worlds.emptyState.title": "دنیا منتظر است",
+ "error.startup.description": "The local project or database could not be opened. Reload and try again.",
+ "error.startup.storageUnavailable": "Local storage could not be opened. Reload and try again.",
+ "error.startup.projectUnavailable": "A local project could not be opened. Reload and try again.",
+ "error.startup.reload": "Reload",
+ "error.startup.recover": "Quarantine project and reload",
+ "error.startup.recovering": "Preserving project…",
+ "error.startup.reset": "Reset database and reload",
+ "error.startup.quarantineNotice": "The complete project folder will be moved to quarantine. No project data will be deleted.",
+ "error.startup.recoveryFailed": "Project preservation failed. The original project was not deleted. Reload and try again.",
+ "error.startup.recoveryUnknown": "Project preservation could not be confirmed. Reload and try again.",
+ "error.startup.recoveryAlreadyPreserved": "The project appears to have been preserved by another recovery attempt. Reload to continue.",
+ "error.startup.resetWarning": "Resetting the database will delete all local projects and settings.",
"copilot.annotationCount": "{{count}} بینش برای این فصل",
"copilot.announceClosed": "AI Copilot بسته شد",
"copilot.announceOpened": "AI Copilot باز شد",
diff --git a/public/locales/fi/bundle.json b/public/locales/fi/bundle.json
index e57df0d80..de65cd7af 100644
--- a/public/locales/fi/bundle.json
+++ b/public/locales/fi/bundle.json
@@ -812,6 +812,18 @@
"voice.stopListening": "Lopeta kuunteleminen",
"worlds.emptyState.description": "Rakenna paikat, säännöt ja historiat, joissa tarinasi elää. Aloita sijainnista.",
"worlds.emptyState.title": "Maailma odottaa",
+ "error.startup.description": "The local project or database could not be opened. Reload and try again.",
+ "error.startup.storageUnavailable": "Local storage could not be opened. Reload and try again.",
+ "error.startup.projectUnavailable": "A local project could not be opened. Reload and try again.",
+ "error.startup.reload": "Reload",
+ "error.startup.recover": "Quarantine project and reload",
+ "error.startup.recovering": "Preserving project…",
+ "error.startup.reset": "Reset database and reload",
+ "error.startup.quarantineNotice": "The complete project folder will be moved to quarantine. No project data will be deleted.",
+ "error.startup.recoveryFailed": "Project preservation failed. The original project was not deleted. Reload and try again.",
+ "error.startup.recoveryUnknown": "Project preservation could not be confirmed. Reload and try again.",
+ "error.startup.recoveryAlreadyPreserved": "The project appears to have been preserved by another recovery attempt. Reload to continue.",
+ "error.startup.resetWarning": "Resetting the database will delete all local projects and settings.",
"copilot.annotationCount": "{{count}} tietoa tästä luvusta",
"copilot.announceClosed": "AI Copilot suljettu",
"copilot.announceOpened": "AI Copilot avattiin",
diff --git a/public/locales/fr/bundle.json b/public/locales/fr/bundle.json
index 22d930b1d..d6f017439 100644
--- a/public/locales/fr/bundle.json
+++ b/public/locales/fr/bundle.json
@@ -812,6 +812,18 @@
"voice.stopListening": "Arrêter l’écoute",
"worlds.emptyState.description": "Construisez les lieux, les règles et les histoires dans lesquels votre récit prend vie. Commencez par un lieu.",
"worlds.emptyState.title": "Le monde vous attend",
+ "error.startup.description": "Le projet local ou la base de données n’a pas pu être ouvert. Rechargez l’application et réessayez.",
+ "error.startup.storageUnavailable": "Le stockage local n’a pas pu être ouvert. Rechargez l’application et réessayez.",
+ "error.startup.projectUnavailable": "Un projet local n’a pas pu être ouvert. Rechargez l’application et réessayez.",
+ "error.startup.reload": "Recharger",
+ "error.startup.recover": "Mettre le projet en quarantaine et recharger",
+ "error.startup.recovering": "Préservation du projet…",
+ "error.startup.reset": "Réinitialiser la base de données et recharger",
+ "error.startup.quarantineNotice": "Le dossier complet du projet sera déplacé en quarantaine. Aucune donnée du projet ne sera supprimée.",
+ "error.startup.recoveryFailed": "La préservation du projet a échoué. Le projet d’origine n’a pas été supprimé. Rechargez l’application et réessayez.",
+ "error.startup.recoveryUnknown": "La préservation du projet n’a pas pu être confirmée. Rechargez l’application et réessayez.",
+ "error.startup.recoveryAlreadyPreserved": "Le projet semble avoir été préservé par une autre tentative de récupération. Rechargez l’application pour continuer.",
+ "error.startup.resetWarning": "La réinitialisation de la base de données supprimera tous les projets et réglages locaux.",
"copilot.annotationCount": "{{count}} insight for this chapter",
"copilot.announceClosed": "Copilot IA fermé",
"copilot.announceOpened": "Copilot IA ouvert",
diff --git a/public/locales/he/bundle.json b/public/locales/he/bundle.json
index 5c6d014a1..7c484db70 100644
--- a/public/locales/he/bundle.json
+++ b/public/locales/he/bundle.json
@@ -812,6 +812,18 @@
"voice.stopListening": "עצירת האזנה",
"worlds.emptyState.description": "בנו את המקומות, החוקים וההיסטוריות שבהם הסיפור שלכם חי. התחילו במיקום.",
"worlds.emptyState.title": "העולם ממתין",
+ "error.startup.description": "The local project or database could not be opened. Reload and try again.",
+ "error.startup.storageUnavailable": "Local storage could not be opened. Reload and try again.",
+ "error.startup.projectUnavailable": "A local project could not be opened. Reload and try again.",
+ "error.startup.reload": "Reload",
+ "error.startup.recover": "Quarantine project and reload",
+ "error.startup.recovering": "Preserving project…",
+ "error.startup.reset": "Reset database and reload",
+ "error.startup.quarantineNotice": "The complete project folder will be moved to quarantine. No project data will be deleted.",
+ "error.startup.recoveryFailed": "Project preservation failed. The original project was not deleted. Reload and try again.",
+ "error.startup.recoveryUnknown": "Project preservation could not be confirmed. Reload and try again.",
+ "error.startup.recoveryAlreadyPreserved": "The project appears to have been preserved by another recovery attempt. Reload to continue.",
+ "error.startup.resetWarning": "Resetting the database will delete all local projects and settings.",
"copilot.annotationCount": "{{count}} insight for this chapter",
"copilot.announceClosed": "AI Copilot closed",
"copilot.announceOpened": "AI Copilot opened",
diff --git a/public/locales/hu/bundle.json b/public/locales/hu/bundle.json
index f779a2cea..7b6e27317 100644
--- a/public/locales/hu/bundle.json
+++ b/public/locales/hu/bundle.json
@@ -812,6 +812,18 @@
"voice.stopListening": "Ne hallgasson",
"worlds.emptyState.description": "Építsd fel azokat a helyeket, szabályokat és történeteket, amelyekben a történeted él. Kezdd egy hellyel.",
"worlds.emptyState.title": "A világ vár",
+ "error.startup.description": "The local project or database could not be opened. Reload and try again.",
+ "error.startup.storageUnavailable": "Local storage could not be opened. Reload and try again.",
+ "error.startup.projectUnavailable": "A local project could not be opened. Reload and try again.",
+ "error.startup.reload": "Reload",
+ "error.startup.recover": "Quarantine project and reload",
+ "error.startup.recovering": "Preserving project…",
+ "error.startup.reset": "Reset database and reload",
+ "error.startup.quarantineNotice": "The complete project folder will be moved to quarantine. No project data will be deleted.",
+ "error.startup.recoveryFailed": "Project preservation failed. The original project was not deleted. Reload and try again.",
+ "error.startup.recoveryUnknown": "Project preservation could not be confirmed. Reload and try again.",
+ "error.startup.recoveryAlreadyPreserved": "The project appears to have been preserved by another recovery attempt. Reload to continue.",
+ "error.startup.resetWarning": "Resetting the database will delete all local projects and settings.",
"copilot.annotationCount": "{{count}} betekintést nyújt ehhez a fejezethez",
"copilot.announceClosed": "Az AI másodpilóta zárva",
"copilot.announceOpened": "Az AI másodpilóta megnyílt",
diff --git a/public/locales/is/bundle.json b/public/locales/is/bundle.json
index 9ba1aad4f..d42fc65ae 100644
--- a/public/locales/is/bundle.json
+++ b/public/locales/is/bundle.json
@@ -812,6 +812,18 @@
"voice.stopListening": "Hættu að hlusta",
"worlds.emptyState.description": "Búðu til staðina, reglurnar og söguna sem sagan þín býr í. Byrjaðu á staðsetningu.",
"worlds.emptyState.title": "Heimurinn bíður",
+ "error.startup.description": "The local project or database could not be opened. Reload and try again.",
+ "error.startup.storageUnavailable": "Local storage could not be opened. Reload and try again.",
+ "error.startup.projectUnavailable": "A local project could not be opened. Reload and try again.",
+ "error.startup.reload": "Reload",
+ "error.startup.recover": "Quarantine project and reload",
+ "error.startup.recovering": "Preserving project…",
+ "error.startup.reset": "Reset database and reload",
+ "error.startup.quarantineNotice": "The complete project folder will be moved to quarantine. No project data will be deleted.",
+ "error.startup.recoveryFailed": "Project preservation failed. The original project was not deleted. Reload and try again.",
+ "error.startup.recoveryUnknown": "Project preservation could not be confirmed. Reload and try again.",
+ "error.startup.recoveryAlreadyPreserved": "The project appears to have been preserved by another recovery attempt. Reload to continue.",
+ "error.startup.resetWarning": "Resetting the database will delete all local projects and settings.",
"copilot.annotationCount": "{{count}} innsýn fyrir þennan kafla",
"copilot.announceClosed": "AI Copilot lokað",
"copilot.announceOpened": "AI Copilot opnaður",
diff --git a/public/locales/it/bundle.json b/public/locales/it/bundle.json
index 6e6a5c7f7..ec6d6fb4f 100644
--- a/public/locales/it/bundle.json
+++ b/public/locales/it/bundle.json
@@ -812,6 +812,18 @@
"voice.stopListening": "Ferma ascolto",
"worlds.emptyState.description": "Costruisci i luoghi, le regole e le storie in cui vive la tua narrazione. Inizia con una location.",
"worlds.emptyState.title": "Il mondo ti aspetta",
+ "error.startup.description": "Non è stato possibile aprire il progetto locale o il database. Ricarica l’applicazione e riprova.",
+ "error.startup.storageUnavailable": "Non è stato possibile aprire l’archiviazione locale. Ricarica l’applicazione e riprova.",
+ "error.startup.projectUnavailable": "Non è stato possibile aprire un progetto locale. Ricarica l’applicazione e riprova.",
+ "error.startup.reload": "Ricarica",
+ "error.startup.recover": "Metti il progetto in quarantena e ricarica",
+ "error.startup.recovering": "Conservazione del progetto…",
+ "error.startup.reset": "Reimposta il database e ricarica",
+ "error.startup.quarantineNotice": "La cartella completa del progetto verrà spostata in quarantena. Nessun dato del progetto verrà eliminato.",
+ "error.startup.recoveryFailed": "La conservazione del progetto non è riuscita. Il progetto originale non è stato eliminato. Ricarica l’applicazione e riprova.",
+ "error.startup.recoveryUnknown": "Non è stato possibile confermare la conservazione del progetto. Ricarica l’applicazione e riprova.",
+ "error.startup.recoveryAlreadyPreserved": "Sembra che un altro tentativo di recupero abbia già conservato il progetto. Ricarica per continuare.",
+ "error.startup.resetWarning": "La reimpostazione del database eliminerà tutti i progetti e le impostazioni locali.",
"copilot.annotationCount": "{{count}} insight for this chapter",
"copilot.announceClosed": "Copilota IA chiuso",
"copilot.announceOpened": "Copilota IA aperto",
diff --git a/public/locales/ja/bundle.json b/public/locales/ja/bundle.json
index 58e4b9ac1..8c78269ab 100644
--- a/public/locales/ja/bundle.json
+++ b/public/locales/ja/bundle.json
@@ -812,6 +812,18 @@
"voice.stopListening": "聞くのをやめる",
"worlds.emptyState.description": "あなたの物語が生きる場所、ルール、歴史を構築します。場所から始めます。",
"worlds.emptyState.title": "世界が待っています",
+ "error.startup.description": "The local project or database could not be opened. Reload and try again.",
+ "error.startup.storageUnavailable": "Local storage could not be opened. Reload and try again.",
+ "error.startup.projectUnavailable": "A local project could not be opened. Reload and try again.",
+ "error.startup.reload": "Reload",
+ "error.startup.recover": "Quarantine project and reload",
+ "error.startup.recovering": "Preserving project…",
+ "error.startup.reset": "Reset database and reload",
+ "error.startup.quarantineNotice": "The complete project folder will be moved to quarantine. No project data will be deleted.",
+ "error.startup.recoveryFailed": "Project preservation failed. The original project was not deleted. Reload and try again.",
+ "error.startup.recoveryUnknown": "Project preservation could not be confirmed. Reload and try again.",
+ "error.startup.recoveryAlreadyPreserved": "The project appears to have been preserved by another recovery attempt. Reload to continue.",
+ "error.startup.resetWarning": "Resetting the database will delete all local projects and settings.",
"copilot.annotationCount": "{{count}} この章の洞察",
"copilot.announceClosed": "AI コパイロットは終了しました",
"copilot.announceOpened": "AI Copilot がオープンしました",
diff --git a/public/locales/ko/bundle.json b/public/locales/ko/bundle.json
index 0f329e17f..4e4ae470d 100644
--- a/public/locales/ko/bundle.json
+++ b/public/locales/ko/bundle.json
@@ -812,6 +812,18 @@
"voice.stopListening": "듣기 중지",
"worlds.emptyState.description": "당신의 이야기가 담긴 장소, 규칙, 역사를 만들어 보세요. 위치부터 시작하세요.",
"worlds.emptyState.title": "세계가 기다리고 있다",
+ "error.startup.description": "The local project or database could not be opened. Reload and try again.",
+ "error.startup.storageUnavailable": "Local storage could not be opened. Reload and try again.",
+ "error.startup.projectUnavailable": "A local project could not be opened. Reload and try again.",
+ "error.startup.reload": "Reload",
+ "error.startup.recover": "Quarantine project and reload",
+ "error.startup.recovering": "Preserving project…",
+ "error.startup.reset": "Reset database and reload",
+ "error.startup.quarantineNotice": "The complete project folder will be moved to quarantine. No project data will be deleted.",
+ "error.startup.recoveryFailed": "Project preservation failed. The original project was not deleted. Reload and try again.",
+ "error.startup.recoveryUnknown": "Project preservation could not be confirmed. Reload and try again.",
+ "error.startup.recoveryAlreadyPreserved": "The project appears to have been preserved by another recovery attempt. Reload to continue.",
+ "error.startup.resetWarning": "Resetting the database will delete all local projects and settings.",
"copilot.annotationCount": "{{count}} 이 장에 대한 통찰력",
"copilot.announceClosed": "AI 부조종사 폐쇄",
"copilot.announceOpened": "AI 코파일럿 오픈",
diff --git a/public/locales/pt/bundle.json b/public/locales/pt/bundle.json
index f451d8c25..6e84c85bd 100644
--- a/public/locales/pt/bundle.json
+++ b/public/locales/pt/bundle.json
@@ -812,6 +812,18 @@
"voice.stopListening": "Pare de ouvir",
"worlds.emptyState.description": "Construa os lugares, regras e histórias em que sua história vive. Comece com um local.",
"worlds.emptyState.title": "O mundo espera",
+ "error.startup.description": "The local project or database could not be opened. Reload and try again.",
+ "error.startup.storageUnavailable": "Local storage could not be opened. Reload and try again.",
+ "error.startup.projectUnavailable": "A local project could not be opened. Reload and try again.",
+ "error.startup.reload": "Reload",
+ "error.startup.recover": "Quarantine project and reload",
+ "error.startup.recovering": "Preserving project…",
+ "error.startup.reset": "Reset database and reload",
+ "error.startup.quarantineNotice": "The complete project folder will be moved to quarantine. No project data will be deleted.",
+ "error.startup.recoveryFailed": "Project preservation failed. The original project was not deleted. Reload and try again.",
+ "error.startup.recoveryUnknown": "Project preservation could not be confirmed. Reload and try again.",
+ "error.startup.recoveryAlreadyPreserved": "The project appears to have been preserved by another recovery attempt. Reload to continue.",
+ "error.startup.resetWarning": "Resetting the database will delete all local projects and settings.",
"copilot.annotationCount": "{{count}} visão para este capítulo",
"copilot.announceClosed": "Copiloto AI fechado",
"copilot.announceOpened": "Copiloto AI aberto",
diff --git a/public/locales/ru/bundle.json b/public/locales/ru/bundle.json
index 9bfa0ea85..9b9d0ad76 100644
--- a/public/locales/ru/bundle.json
+++ b/public/locales/ru/bundle.json
@@ -812,6 +812,18 @@
"voice.stopListening": "Хватит слушать",
"worlds.emptyState.description": "Создайте места, правила и историю, в которых живет ваша история. Начните с локации.",
"worlds.emptyState.title": "Мир ждет",
+ "error.startup.description": "The local project or database could not be opened. Reload and try again.",
+ "error.startup.storageUnavailable": "Local storage could not be opened. Reload and try again.",
+ "error.startup.projectUnavailable": "A local project could not be opened. Reload and try again.",
+ "error.startup.reload": "Reload",
+ "error.startup.recover": "Quarantine project and reload",
+ "error.startup.recovering": "Preserving project…",
+ "error.startup.reset": "Reset database and reload",
+ "error.startup.quarantineNotice": "The complete project folder will be moved to quarantine. No project data will be deleted.",
+ "error.startup.recoveryFailed": "Project preservation failed. The original project was not deleted. Reload and try again.",
+ "error.startup.recoveryUnknown": "Project preservation could not be confirmed. Reload and try again.",
+ "error.startup.recoveryAlreadyPreserved": "The project appears to have been preserved by another recovery attempt. Reload to continue.",
+ "error.startup.resetWarning": "Resetting the database will delete all local projects and settings.",
"copilot.annotationCount": "{{count}} информация по этой главе",
"copilot.announceClosed": "AI второй пилот закрыт",
"copilot.announceOpened": "AI второй пилот открыт",
diff --git a/public/locales/sv/bundle.json b/public/locales/sv/bundle.json
index edab8844a..aed1cfcf4 100644
--- a/public/locales/sv/bundle.json
+++ b/public/locales/sv/bundle.json
@@ -812,6 +812,18 @@
"voice.stopListening": "Sluta lyssna",
"worlds.emptyState.description": "Bygg upp platserna, reglerna och historien som din berättelse lever i. Börja med en plats.",
"worlds.emptyState.title": "Världen väntar",
+ "error.startup.description": "The local project or database could not be opened. Reload and try again.",
+ "error.startup.storageUnavailable": "Local storage could not be opened. Reload and try again.",
+ "error.startup.projectUnavailable": "A local project could not be opened. Reload and try again.",
+ "error.startup.reload": "Reload",
+ "error.startup.recover": "Quarantine project and reload",
+ "error.startup.recovering": "Preserving project…",
+ "error.startup.reset": "Reset database and reload",
+ "error.startup.quarantineNotice": "The complete project folder will be moved to quarantine. No project data will be deleted.",
+ "error.startup.recoveryFailed": "Project preservation failed. The original project was not deleted. Reload and try again.",
+ "error.startup.recoveryUnknown": "Project preservation could not be confirmed. Reload and try again.",
+ "error.startup.recoveryAlreadyPreserved": "The project appears to have been preserved by another recovery attempt. Reload to continue.",
+ "error.startup.resetWarning": "Resetting the database will delete all local projects and settings.",
"copilot.annotationCount": "{{count}} insikt för detta kapitel",
"copilot.announceClosed": "AI Copilot stängd",
"copilot.announceOpened": "AI Copilot öppnade",
diff --git a/public/locales/zh/bundle.json b/public/locales/zh/bundle.json
index a8c487bc5..43abb3b5a 100644
--- a/public/locales/zh/bundle.json
+++ b/public/locales/zh/bundle.json
@@ -812,6 +812,18 @@
"voice.stopListening": "停止聆听",
"worlds.emptyState.description": "构建你的故事所存在的地点、规则和历史。从一个地点开始。",
"worlds.emptyState.title": "世界等待着",
+ "error.startup.description": "The local project or database could not be opened. Reload and try again.",
+ "error.startup.storageUnavailable": "Local storage could not be opened. Reload and try again.",
+ "error.startup.projectUnavailable": "A local project could not be opened. Reload and try again.",
+ "error.startup.reload": "Reload",
+ "error.startup.recover": "Quarantine project and reload",
+ "error.startup.recovering": "Preserving project…",
+ "error.startup.reset": "Reset database and reload",
+ "error.startup.quarantineNotice": "The complete project folder will be moved to quarantine. No project data will be deleted.",
+ "error.startup.recoveryFailed": "Project preservation failed. The original project was not deleted. Reload and try again.",
+ "error.startup.recoveryUnknown": "Project preservation could not be confirmed. Reload and try again.",
+ "error.startup.recoveryAlreadyPreserved": "The project appears to have been preserved by another recovery attempt. Reload to continue.",
+ "error.startup.resetWarning": "Resetting the database will delete all local projects and settings.",
"copilot.annotationCount": "{{count}} 本章见解",
"copilot.announceClosed": "AI副驾驶关闭",
"copilot.announceOpened": "AI副驾驶开启",
diff --git a/services/startupRecovery.tsx b/services/startupRecovery.tsx
new file mode 100644
index 000000000..d0283a3d8
--- /dev/null
+++ b/services/startupRecovery.tsx
@@ -0,0 +1,63 @@
+import React from 'react';
+import type { Root } from 'react-dom/client';
+import { loadStorageErrorCopy, StorageErrorScreen } from '../components/StorageErrorScreen';
+import { resetAllDatabases } from './dbInitialization';
+import { ProjectLoadError, ProjectQuarantineError } from './fs/projectFsStore';
+import { logger } from './logger';
+import { getStartupRecoveryActions } from './startupRecoveryPolicy';
+import { storageService } from './storageService';
+
+// QNBS-v3: keep startup recovery render decisions testable so destructive reset stays bound to backend provenance.
+export async function renderStorageInitializationFailure(root: Root): Promise {
+ const copy = await loadStorageErrorCopy();
+ root.render(
+
+ {
+ await resetAllDatabases();
+ window.location.reload();
+ }}
+ />
+ ,
+ );
+}
+
+// QNBS-v3: filesystem failures get preserve-first actions while only IndexedDB failures retain destructive reset authority.
+export async function renderProjectInitializationFailure(
+ root: Root,
+ error: unknown,
+): Promise {
+ logger.error('Failed to initialize the application:', error);
+ const projectLoadError = error instanceof ProjectLoadError ? error : null;
+ const backendKind = await storageService.getStorageBackendKind();
+ const copy = await loadStorageErrorCopy();
+ const { canQuarantine, canReset } = getStartupRecoveryActions(error, backendKind);
+ const corruptProjectId =
+ projectLoadError?.reason === 'corrupt' ? projectLoadError.projectId : null;
+ root.render(
+
+ {
+ if (!corruptProjectId) throw new ProjectQuarantineError('io-error');
+ const result = await storageService.quarantineProject(corruptProjectId);
+ if (!result) throw new ProjectQuarantineError('io-error');
+ window.location.reload();
+ },
+ }
+ : {})}
+ {...(canReset
+ ? {
+ onReset: async () => {
+ await resetAllDatabases();
+ window.location.reload();
+ },
+ }
+ : {})}
+ />
+ ,
+ );
+}
diff --git a/services/startupRecoveryPolicy.ts b/services/startupRecoveryPolicy.ts
new file mode 100644
index 000000000..4fede0c95
--- /dev/null
+++ b/services/startupRecoveryPolicy.ts
@@ -0,0 +1,15 @@
+import { ProjectLoadError } from './fs/projectFsStore';
+
+export type StartupStorageBackend = 'indexeddb' | 'filesystem';
+
+// QNBS-v3: bind destructive reset to IndexedDB provenance so desktop filesystem failures stay preserve-first.
+export function getStartupRecoveryActions(
+ error: unknown,
+ backend: StartupStorageBackend,
+): { canQuarantine: boolean; canReset: boolean } {
+ const projectLoadError = error instanceof ProjectLoadError ? error : null;
+ return {
+ canQuarantine: projectLoadError?.reason === 'corrupt' && backend === 'filesystem',
+ canReset: !projectLoadError && backend === 'indexeddb',
+ };
+}
diff --git a/tests/unit/StorageErrorScreen.test.tsx b/tests/unit/StorageErrorScreen.test.tsx
new file mode 100644
index 000000000..b32627f1c
--- /dev/null
+++ b/tests/unit/StorageErrorScreen.test.tsx
@@ -0,0 +1,142 @@
+import { render, screen, waitFor } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import { beforeEach, describe, expect, it, vi } from 'vitest';
+import {
+ loadStorageErrorCopy,
+ STARTUP_COPY_FALLBACKS,
+ StorageErrorScreen,
+} from '../../components/StorageErrorScreen';
+import { ProjectQuarantineError } from '../../services/fs/projectFsStore';
+
+const { getStaticTranslation, loggerError } = vi.hoisted(() => ({
+ getStaticTranslation: vi.fn(),
+ loggerError: vi.fn(),
+}));
+
+vi.mock('../../services/i18n/staticTranslate', () => ({ getStaticTranslation }));
+vi.mock('../../services/logger', async (importOriginal) => {
+ const actual = await importOriginal();
+ return {
+ ...actual,
+ logger: { debug: vi.fn(), warn: vi.fn(), info: vi.fn(), error: loggerError },
+ };
+});
+
+// QNBS-v3: preserve truthful, accessible recovery outcomes while preventing destructive action races.
+describe('StorageErrorScreen', () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ getStaticTranslation.mockImplementation((key: string) => Promise.resolve(`translated:${key}`));
+ });
+
+ it('loads translated recovery copy and falls back safely when a key is unavailable', async () => {
+ await expect(loadStorageErrorCopy()).resolves.toMatchObject({
+ description: 'translated:error.startup.description',
+ recoveryAlreadyPreserved: 'translated:error.startup.recoveryAlreadyPreserved',
+ });
+
+ getStaticTranslation.mockImplementation((key: string) => Promise.resolve(key));
+ await expect(loadStorageErrorCopy()).resolves.toEqual(STARTUP_COPY_FALLBACKS);
+ });
+
+ it('renders localized actions and never renders a raw recovery exception', async () => {
+ const user = userEvent.setup();
+ const onRecover = vi.fn().mockRejectedValue(new Error('EACCES /private/project.json'));
+ const copy = {
+ ...STARTUP_COPY_FALLBACKS,
+ description: 'Localized description',
+ projectUnavailable: 'Localized project failure',
+ recover: 'Localized preserve',
+ recoveryFailed: 'Localized safe failure',
+ };
+
+ render();
+ expect(screen.getByText('Localized description')).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: 'Localized preserve' })).toBeInTheDocument();
+
+ await user.click(screen.getByRole('button', { name: 'Localized preserve' }));
+
+ expect(await screen.findByText('Localized safe failure')).toBeInTheDocument();
+ expect(screen.queryByText('EACCES /private/project.json')).not.toBeInTheDocument();
+ expect(loggerError).toHaveBeenCalledWith(
+ 'Project quarantine failed',
+ expect.objectContaining({ error: 'EACCES /private/project.json' }),
+ );
+ });
+
+ it('announces recovery and blocks destructive reset while preservation is pending', async () => {
+ const user = userEvent.setup();
+ let resolveRecovery: () => void = () => undefined;
+ const onRecover = vi.fn(
+ () =>
+ new Promise((resolve) => {
+ resolveRecovery = resolve;
+ }),
+ );
+ const onReset = vi.fn();
+
+ render(
+ ,
+ );
+
+ await user.click(screen.getByRole('button', { name: STARTUP_COPY_FALLBACKS.recover }));
+
+ expect(screen.getByRole('status')).toHaveTextContent(STARTUP_COPY_FALLBACKS.recovering);
+ expect(screen.getByRole('button', { name: STARTUP_COPY_FALLBACKS.reset })).toBeDisabled();
+ await user.click(screen.getByRole('button', { name: STARTUP_COPY_FALLBACKS.reset }));
+ expect(onReset).not.toHaveBeenCalled();
+
+ resolveRecovery();
+ await waitFor(() => expect(screen.getByRole('status')).toHaveTextContent(''));
+ });
+
+ it('keeps source-missing wording truthful when preservation cannot be confirmed', async () => {
+ const user = userEvent.setup();
+ const onRecover = vi.fn().mockRejectedValue(new ProjectQuarantineError('source-missing'));
+
+ render();
+
+ await user.click(screen.getByRole('button', { name: STARTUP_COPY_FALLBACKS.recover }));
+
+ expect(await screen.findByText(STARTUP_COPY_FALLBACKS.recoveryUnknown)).toBeInTheDocument();
+ expect(screen.queryByText(STARTUP_COPY_FALLBACKS.recoveryFailed)).not.toBeInTheDocument();
+ });
+
+ it('reports the typed already-preserved outcome and supports successful recovery and reset actions', async () => {
+ const user = userEvent.setup();
+ const onRecover = vi.fn().mockRejectedValue(new ProjectQuarantineError('already-preserved'));
+ const onReset = vi.fn();
+ const firstRender = render(
+ ,
+ );
+
+ await user.click(screen.getByRole('button', { name: STARTUP_COPY_FALLBACKS.recover }));
+ expect(
+ await screen.findByText(STARTUP_COPY_FALLBACKS.recoveryAlreadyPreserved),
+ ).toBeInTheDocument();
+
+ await user.click(screen.getByRole('button', { name: STARTUP_COPY_FALLBACKS.reset }));
+ expect(onReset).toHaveBeenCalledOnce();
+
+ firstRender.unmount();
+ const successfulRecover = vi.fn().mockResolvedValue(undefined);
+ render();
+ await user.click(screen.getByRole('button', { name: STARTUP_COPY_FALLBACKS.recover }));
+ await waitFor(() => expect(successfulRecover).toHaveBeenCalledOnce());
+ expect(
+ screen.queryByRole('button', { name: STARTUP_COPY_FALLBACKS.reset }),
+ ).not.toBeInTheDocument();
+ });
+
+ it('omits unsupported recovery actions when no handlers are provided', () => {
+ render();
+
+ expect(
+ screen.queryByRole('button', { name: STARTUP_COPY_FALLBACKS.recover }),
+ ).not.toBeInTheDocument();
+ expect(
+ screen.queryByRole('button', { name: STARTUP_COPY_FALLBACKS.reset }),
+ ).not.toBeInTheDocument();
+ expect(screen.getByText(STARTUP_COPY_FALLBACKS.storageUnavailable)).toBeInTheDocument();
+ });
+});
diff --git a/tests/unit/startupRecovery.test.tsx b/tests/unit/startupRecovery.test.tsx
new file mode 100644
index 000000000..19f657920
--- /dev/null
+++ b/tests/unit/startupRecovery.test.tsx
@@ -0,0 +1,134 @@
+import React from 'react';
+import { beforeEach, describe, expect, it, vi } from 'vitest';
+
+const { mockRoot, mockReset, mockBackendKind, mockQuarantine, mockCopy, loggerError } = vi.hoisted(
+ () => ({
+ mockRoot: { render: vi.fn() },
+ mockReset: vi.fn().mockResolvedValue(undefined),
+ mockBackendKind: vi.fn(),
+ mockQuarantine: vi.fn(),
+ mockCopy: {
+ description: 'description',
+ storageUnavailable: 'storage unavailable',
+ projectUnavailable: 'project unavailable',
+ reload: 'reload',
+ recover: 'recover',
+ recovering: 'recovering',
+ reset: 'reset',
+ quarantineNotice: 'quarantine notice',
+ recoveryFailed: 'recovery failed',
+ recoveryUnknown: 'recovery unknown',
+ recoveryAlreadyPreserved: 'already preserved',
+ resetWarning: 'reset warning',
+ },
+ loggerError: vi.fn(),
+ }),
+);
+
+vi.mock('../../services/dbInitialization', () => ({ resetAllDatabases: mockReset }));
+vi.mock('../../services/storageService', () => ({
+ storageService: {
+ getStorageBackendKind: mockBackendKind,
+ quarantineProject: mockQuarantine,
+ },
+}));
+vi.mock('../../services/i18n/staticTranslate', () => ({
+ getStaticTranslation: (key: string) => Promise.resolve(key),
+}));
+vi.mock('../../services/logger', () => ({
+ logger: { debug: vi.fn(), warn: vi.fn(), info: vi.fn(), error: loggerError },
+}));
+vi.mock('../../components/StorageErrorScreen', () => ({
+ loadStorageErrorCopy: () => Promise.resolve(mockCopy),
+ StorageErrorScreen: () => React.createElement('div'),
+}));
+vi.mock('../../services/fs/projectFsStore', () => {
+ class ProjectLoadError extends Error {
+ constructor(
+ public readonly reason: 'corrupt' | 'io-error',
+ message: string,
+ public readonly projectId: string,
+ ) {
+ super(message);
+ this.name = 'ProjectLoadError';
+ }
+ }
+ class ProjectQuarantineError extends Error {
+ constructor(public readonly reason: string) {
+ super('safe quarantine failure');
+ this.name = 'ProjectQuarantineError';
+ }
+ }
+ return { ProjectLoadError, ProjectQuarantineError };
+});
+
+import { ProjectLoadError } from '../../services/fs/projectFsStore';
+import {
+ renderProjectInitializationFailure,
+ renderStorageInitializationFailure,
+} from '../../services/startupRecovery';
+
+type RecoveryScreenProps = {
+ copy: typeof mockCopy;
+ onReset?: () => Promise;
+ onRecover?: () => Promise;
+};
+
+function renderedScreenProps(): RecoveryScreenProps {
+ const strictMode = mockRoot.render.mock.calls.at(-1)?.[0] as React.ReactElement<{
+ children: React.ReactElement;
+ }>;
+ return strictMode.props.children.props;
+}
+
+// QNBS-v3: keep startup recovery actions constrained by the originating storage backend.
+describe('startup recovery rendering', () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ });
+
+ it('renders the database reset screen when IndexedDB initialization fails', async () => {
+ await renderStorageInitializationFailure(mockRoot as never);
+
+ expect(mockRoot.render).toHaveBeenCalledOnce();
+ expect(renderedScreenProps().copy).toBe(mockCopy);
+ expect(renderedScreenProps().onReset).toEqual(expect.any(Function));
+ });
+
+ it('renders quarantine for corrupt filesystem projects and preserves the exact project ID', async () => {
+ mockBackendKind.mockResolvedValue('filesystem');
+ mockQuarantine.mockResolvedValue({ projectId: 'p1', path: '/quarantine/p1' });
+ await renderProjectInitializationFailure(
+ mockRoot as never,
+ new ProjectLoadError('corrupt', 'corrupt', 'p1'),
+ );
+
+ const props = renderedScreenProps();
+ expect(props.onRecover).toEqual(expect.any(Function));
+ expect(props.onReset).toBeUndefined();
+ await (props.onRecover as () => Promise)();
+ expect(mockQuarantine).toHaveBeenCalledWith('p1');
+ });
+
+ it('does not expose reset for raw or project filesystem failures', async () => {
+ mockBackendKind.mockResolvedValue('filesystem');
+
+ await renderProjectInitializationFailure(mockRoot as never, new Error('EACCES /projects/p1'));
+ expect(renderedScreenProps().onReset).toBeUndefined();
+
+ await renderProjectInitializationFailure(
+ mockRoot as never,
+ new ProjectLoadError('io-error', 'io', 'p1'),
+ );
+ expect(renderedScreenProps().onReset).toBeUndefined();
+ expect(loggerError).toHaveBeenCalled();
+ });
+
+ it('retains reset only for a non-project IndexedDB failure', async () => {
+ mockBackendKind.mockResolvedValue('indexeddb');
+ await renderProjectInitializationFailure(mockRoot as never, new Error('QuotaExceededError'));
+
+ expect(renderedScreenProps().onRecover).toBeUndefined();
+ expect(renderedScreenProps().onReset).toEqual(expect.any(Function));
+ });
+});
diff --git a/tests/unit/startupRecoveryPolicy.test.ts b/tests/unit/startupRecoveryPolicy.test.ts
new file mode 100644
index 000000000..a264d83c1
--- /dev/null
+++ b/tests/unit/startupRecoveryPolicy.test.ts
@@ -0,0 +1,39 @@
+import { describe, expect, it } from 'vitest';
+import { ProjectLoadError } from '../../services/fs/projectFsStore';
+import { getStartupRecoveryActions } from '../../services/startupRecoveryPolicy';
+
+// QNBS-v3: prevent filesystem corruption from acquiring destructive database-reset authority.
+describe('startup recovery action policy', () => {
+ it('offers quarantine only for corrupt projects on the filesystem backend', () => {
+ const error = new ProjectLoadError('corrupt', 'corrupt', 'project-1');
+
+ expect(getStartupRecoveryActions(error, 'filesystem')).toEqual({
+ canQuarantine: true,
+ canReset: false,
+ });
+ expect(getStartupRecoveryActions(error, 'indexeddb')).toEqual({
+ canQuarantine: false,
+ canReset: false,
+ });
+ });
+
+ it('never offers destructive reset for filesystem-origin failures, including raw errors', () => {
+ expect(getStartupRecoveryActions(new Error('EACCES /projects/p1'), 'filesystem')).toEqual({
+ canQuarantine: false,
+ canReset: false,
+ });
+ expect(
+ getStartupRecoveryActions(new ProjectLoadError('io-error', 'io', 'project-1'), 'filesystem'),
+ ).toEqual({
+ canQuarantine: false,
+ canReset: false,
+ });
+ });
+
+ it('retains database reset for non-project failures from IndexedDB', () => {
+ expect(getStartupRecoveryActions(new Error('QuotaExceededError'), 'indexeddb')).toEqual({
+ canQuarantine: false,
+ canReset: true,
+ });
+ });
+});