fix(app): repair theme CSS sync persistence and data loss (#2067)

This commit is contained in:
Hampus
2026-08-29 03:37:41 +02:00
committed by GitHub
parent 14de5971d5
commit 9ad026b8ce
4 changed files with 35 additions and 5 deletions
+1 -1
View File
@@ -254,7 +254,7 @@ function normalizeStoredSyncedPreferences(raw: string | null | undefined): strin
try {
decodeSyncedPreferences(raw);
} catch {
return '';
return raw;
}
return raw;
}
@@ -33,6 +33,7 @@ export const ZOOM_LEVEL_MARKERS = [0.5, 0.75, 1.0, 1.25, 1.5, 1.75, 2.0] as cons
export const ACCESSIBILITY_STORE_STORAGE_KEY = 'Accessibility';
export const ACCESSIBILITY_ZOOM_STORAGE_KEY = 'Accessibility:zoomLevel';
export const ACCESSIBILITY_CUSTOM_THEME_STORAGE_KEY = 'Accessibility:customThemeCss';
const ACCESSIBILITY_CUSTOM_THEME_SYNC_STORAGE_KEY = 'Accessibility:customThemeCssSyncAcrossDevices';
export const ACCESSIBILITY_MOTION_STORAGE_KEY = 'Accessibility:motion';
export const ACCESSIBILITY_SHOW_NEKO_STORAGE_KEY = 'Accessibility:showNeko';
export const ACCESSIBILITY_KEEP_NEKO_STILL_STORAGE_KEY = 'Accessibility:keepNekoStill';
@@ -492,6 +493,21 @@ function persistLocalCustomThemeCss(css: string | null): void {
} catch {}
}
function persistLocalCustomThemeCssSyncAcrossDevices(value: boolean): void {
try {
AppStorage.setItem(ACCESSIBILITY_CUSTOM_THEME_SYNC_STORAGE_KEY, JSON.stringify(value));
} catch {}
}
function readLocalCustomThemeCssSyncAcrossDevices(): boolean {
try {
const raw = AppStorage.getItem(ACCESSIBILITY_CUSTOM_THEME_SYNC_STORAGE_KEY);
return raw === null ? false : JSON.parse(raw) === true;
} catch {
return false;
}
}
function normalizeCustomThemeCss(css: string | null | undefined): string | null {
if (typeof css !== 'string') {
return null;
@@ -728,6 +744,7 @@ class Accessibility {
if (this.customThemeCss !== null) {
persistLocalCustomThemeCss(this.customThemeCss);
}
this.customThemeCssSyncAcrossDevices = readLocalCustomThemeCssSyncAcrossDevices();
this.showNeko = readAndMigrateLocalShowNeko();
this.keepNekoStill = readAndMigrateLocalKeepNekoStill();
this.showVideoSeekPreviewThumbnails = readLocalVideoSeekPreviewThumbnails();
@@ -851,7 +868,11 @@ class Accessibility {
showStickersInAutocomplete: s.showStickersInExpressionAutocomplete,
showMemesInAutocomplete: s.showMemesInExpressionAutocomplete,
voiceChannelJoinRequiresDoubleClick: s.voiceChannelJoinRequiresDoubleClick,
customThemeCss: s.customThemeCssSyncAcrossDevices ? (s.customThemeCss ?? '') : (s.serverCustomThemeCss ?? ''),
customThemeCss: ((): string | undefined => {
const local = s.customThemeCss;
const server = s.serverCustomThemeCss;
return (s.customThemeCssSyncAcrossDevices ? local : server) ?? undefined;
})(),
showFavorites: s.showFavorites,
dmMessagePreviewMode: DM_PREVIEW_TO_PROTO[s.dmMessagePreviewMode],
enableTtsCommand: s.enableTTSCommand,
@@ -1396,6 +1417,7 @@ class Accessibility {
persistLocalCustomThemeCss(this.customThemeCss);
}
this.customThemeCssSyncAcrossDevices = syncAcrossDevices;
persistLocalCustomThemeCssSyncAcrossDevices(syncAcrossDevices);
}
subscribe(callback: () => void): () => void {
@@ -125,6 +125,7 @@ class ThemeLibrary {
localFiles: Array<ThemeLibraryLocalFileReference> = [];
enabledThemeIds: Array<string> = [];
isHydrated = false;
loadFailed = false;
isBusy = false;
revision = 0;
private initPromise: Promise<void> | null = null;
@@ -173,12 +174,13 @@ class ThemeLibrary {
this.localFiles = localFiles.sort((a, b) => a.name.localeCompare(b.name));
this.enabledThemeIds = enabledThemeIds.filter((id) => themes.some((theme) => theme.id === id));
this.isHydrated = true;
this.loadFailed = false;
this.revision += 1;
});
} catch (error) {
logger.error('Failed to hydrate theme library', error);
runInAction(() => {
this.isHydrated = true;
this.loadFailed = true;
});
}
}
@@ -94,7 +94,7 @@ function getDatabase(): Promise<IDBDatabase> {
if (openPromise) {
return openPromise;
}
openPromise = new Promise((resolve, reject) => {
const promise = new Promise<IDBDatabase>((resolve, reject) => {
if (!browserIndexedDB) {
reject(new Error('IndexedDB unavailable'));
return;
@@ -126,7 +126,13 @@ function getDatabase(): Promise<IDBDatabase> {
request.onerror = () => reject(request.error ?? new Error('Failed to open theme library database'));
request.onblocked = () => reject(new Error('Theme library database upgrade is blocked by another window'));
});
return openPromise;
promise.catch(() => {
if (openPromise === promise) {
openPromise = null;
}
});
openPromise = promise;
return promise;
}
async function withReadonlyDb<T>(