fix: splash screen hangs after login, never dismisses
All checks were successful
Build and Push Docker Image / build (push) Successful in 28s
CI / npm audit (push) Successful in 45s

Root cause: showSplash() sets display:flex + opacity:1 synchronously,
then dismissSplash() immediately adds the fade-out class (opacity:0).
The browser batches these in the same paint frame so the CSS transition
from opacity:1 -> 0 never starts, and transitionend never fires,
leaving the Promise unresolved and the splash stuck.

Two-part fix:
1. handleLogin: await two requestAnimationFrames between showSplash()
   and dismissSplash() so the browser paints opacity:1 first, ensuring
   the CSS opacity transition actually runs.
2. dismissSplash: add a 500ms fallback setTimeout that hides the splash
   and resolves the Promise even if transitionend is never fired (acts
   as a safety net for any future edge cases).
This commit is contained in:
2026-05-16 17:16:31 +01:00
parent e83afde5ef
commit 11749a428c

View File

@@ -99,7 +99,15 @@ function dismissSplash(startTime) {
setTimeout(() => {
const splash = document.getElementById('splash-screen');
splash.classList.add('fade-out');
// Fallback: resolve after transition duration + buffer in case
// transitionend never fires (e.g. display was toggled in same frame)
const TRANSITION_MS = 400;
const fallback = setTimeout(() => {
splash.style.display = 'none';
resolve();
}, TRANSITION_MS + 100);
splash.addEventListener('transitionend', () => {
clearTimeout(fallback);
splash.style.display = 'none';
resolve();
}, { once: true });
@@ -152,9 +160,13 @@ async function handleLogin(e) {
if (data.success) {
currentUser = data.user;
isAdmin = !!data.user.isAdmin;
// Fade out login, then show splash while loading data
// Fade out login, then show splash while loading data.
// requestAnimationFrame ensures the browser paints the splash at
// opacity:1 before dismissSplash adds fade-out, so the CSS
// transition fires and transitionend is guaranteed.
await fadeOutLogin();
showSplash();
await new Promise(r => requestAnimationFrame(() => requestAnimationFrame(r)));
showDashboard();
const splashStart = Date.now();
await fetchUserDownloads(true);