From 3189be2de05aac1e4cfdbef438646dbf4eabfa87 Mon Sep 17 00:00:00 2001 From: Steven RYDELL Date: Thu, 30 Jul 2026 18:49:35 +0200 Subject: [PATCH] refactor(logo): encapsulate shared logo cache with AbortController cleanup --- CHANGELOG.md | 2 +- src/components/common/Logo.tsx | 18 ++++----- src/lib/logoCache.ts | 73 ++++++++++++++++++++++++++++++++++ src/stores/uiStore.ts | 59 --------------------------- 4 files changed, 82 insertions(+), 70 deletions(-) create mode 100644 src/lib/logoCache.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index f57e779..9c3711a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,7 +25,7 @@ All notable changes to this project will be documented in this file. This projec - Updated Vite to 8.2.0, `@vitejs/plugin-react` to 6.0.5, and `lucide-react` to 1.28.0. ### Fixed -- Custom logos no longer flash the default Stalwart logo while loading. +- Custom logos no longer flash the default Stalwart logo while loading. Loading is encapsulated in `logoCache` (shared fetch + AbortController + blob URL revoke), keeping `uiStore` free of logo state while still caching across TopBar/Login remounts. - Icon/label alignment in backend select triggers. - Web Applications list shows an Enabled column again. - Appearance Corners preview: only the Rounded choice forces rounded radius on its card and sample; Square stays sharp even when the global theme is square. diff --git a/src/components/common/Logo.tsx b/src/components/common/Logo.tsx index 92f0d48..e180817 100644 --- a/src/components/common/Logo.tsx +++ b/src/components/common/Logo.tsx @@ -4,9 +4,9 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -import { useEffect } from 'react'; +import { useEffect, useSyncExternalStore } from 'react'; import { useTranslation } from 'react-i18next'; -import { useUIStore } from '@/stores/uiStore'; +import { ensureLogoLoaded, getLogoSnapshot, subscribeLogo } from '@/lib/logoCache'; export function DefaultLogo() { const { t } = useTranslation(); @@ -31,19 +31,17 @@ export function DefaultLogo() { export default function Logo() { const { t } = useTranslation(); - const logoUrl = useUIStore((s) => s.logoUrl); - const logoLoading = useUIStore((s) => s.logoLoading); - const fetchLogo = useUIStore((s) => s.fetchLogo); + const logo = useSyncExternalStore(subscribeLogo, getLogoSnapshot, getLogoSnapshot); useEffect(() => { - fetchLogo(); - }, [fetchLogo]); + ensureLogoLoaded(); + }, []); - if (logoUrl) { - return {t('logo.alt',; + if (logo.status === 'custom') { + return {t('logo.alt',; } - if (logoLoading) { + if (logo.status === 'loading') { return