feat(settings): add "Refresh cached data" recovery action

When the mailbox view gets into a stale or wrong state, the only escape
was the browser's "clear site data" — which also wipes the saved account
list, forcing a re-login of every account.

Add a non-destructive "Refresh cached data" button under Settings →
Data. It clears the server-derived caches (contacts, calendars,
identities, per-account snapshots) and reloads so they re-fetch fresh,
while preserving accounts, sessions, settings, themes and user content
(templates, S/MIME). Two-click confirm to avoid an accidental reload.

English strings added across all locales (translation follow-up); unit
tests cover the cache-clear (keeps account-registry/auth/prefs) and the
reload.
This commit is contained in:
Shuki Vaknin
2026-07-21 20:58:55 +02:00
committed by Linus Rath
parent f2703bcc27
commit f51ec50443
23 changed files with 216 additions and 0 deletions
@@ -11,6 +11,7 @@ import { useUpdateStore } from '@/stores/update-store';
import { ExternalLink } from 'lucide-react';
import { cn } from '@/lib/utils';
import { getPathPrefix } from '@/lib/browser-navigation';
import { clearCachedData } from '@/lib/clear-cached-data';
import { SpamSiegeGame } from './spam-siege-game';
const APP_VERSION = process.env.NEXT_PUBLIC_APP_VERSION || "0.0.0";
@@ -54,6 +55,7 @@ export function AboutDataSettings() {
useSettingsStore();
const { settingsSyncEnabled } = useConfig();
const [showResetConfirm, setShowResetConfirm] = useState(false);
const [showRefreshConfirm, setShowRefreshConfirm] = useState(false);
const fileInputRef = useRef<HTMLInputElement>(null);
const { isFeatureEnabled } = usePolicyStore();
const [showGame, setShowGame] = useState(false);
@@ -105,6 +107,15 @@ export function AboutDataSettings() {
reader.readAsText(file);
};
const handleRefreshCache = () => {
if (showRefreshConfirm) {
clearCachedData(); // reloads the page
} else {
setShowRefreshConfirm(true);
setTimeout(() => setShowRefreshConfirm(false), 5000);
}
};
const handleReset = () => {
if (showResetConfirm) {
resetToDefaults();
@@ -187,6 +198,16 @@ export function AboutDataSettings() {
</SettingItem>
)}
<SettingItem label={t('refresh_cache.label')} description={t('refresh_cache.description')}>
<Button
variant={showRefreshConfirm ? 'default' : 'outline'}
size="sm"
onClick={handleRefreshCache}
>
{showRefreshConfirm ? tCommon('yes') : t('refresh_cache.button')}
</Button>
</SettingItem>
<SettingItem label={t('reset_settings.label')} description={t('reset_settings.description')}>
<Button
variant={showResetConfirm ? 'destructive' : 'outline'}