diff --git a/frontend/src/routes/+page.svelte b/frontend/src/routes/+page.svelte index 0acd48c..3845141 100644 --- a/frontend/src/routes/+page.svelte +++ b/frontend/src/routes/+page.svelte @@ -96,6 +96,10 @@ let confirmClearEvents = $state(false); let refreshSeconds = $state(loadRefreshSeconds()); let selectedEvent = $state(null); + // Stagger entry animation should play once on initial load only — + // without this, every pagination/filter change re-runs the cascade + // (~600ms of fade-up per row) which reads as the panel "reconstructing". + let eventsAnimated = $state(false); // Auto-refresh ticker — re-creates the interval whenever the user // changes the cadence. ``$effect`` returns a cleanup that fires on @@ -279,6 +283,16 @@ } } + // Disable stagger entry animation once the first non-empty list has + // rendered + had time to play. Subsequent pagination/filter reloads + // then settle in place instead of re-running the cascade. + $effect(() => { + if (eventsAnimated) return; + if (!status?.recent_events?.length) return; + const handle = setTimeout(() => { eventsAnimated = true; }, 700); + return () => clearTimeout(handle); + }); + const filteredProviderCount = $derived(globalProviderFilter.providerType ? providers.filter(p => p.type === globalProviderFilter.providerType).length : displayProviders); @@ -675,18 +689,23 @@ {/snippet} - {#if eventsLoading} -

{t('dashboard.loadingEvents')}

- {:else if status.recent_events.length === 0} -
- -

{t('dashboard.noEvents')}

-
+ {#if status.recent_events.length === 0} + {#if eventsLoading} +

{t('dashboard.loadingEvents')}

+ {:else} +
+ +

{t('dashboard.noEvents')}

+
+ {/if} {:else} -
+
{#each status.recent_events as event, i (event.id)}