From 515678f0359a204cd673f73e160fd7fd401da9c1 Mon Sep 17 00:00:00 2001 From: John O'Keefe Date: Wed, 16 Sep 2026 16:30:08 -0400 Subject: [PATCH] fix(frontend): Svelte 5 mount API and route-driven refresh - loader action used new Component() plus $destroy, both removed in Svelte 5 (component_api_invalid_new crashed every refresh click before the reload ran); rewritten on mount/unmount with a real action destroy for the leaked subscription - homecoming refresh moves off the legacy $: block, which never tracked the runes-backed router.location, onto the sanctioned onRouteLoaded callback with identical guard semantics; unused router import dropped so the check stays warning-free --- frontend/src/App.svelte | 36 ++++++++++++++++---------- frontend/src/helperFunctions/loader.ts | 33 +++++++++++++++-------- 2 files changed, 44 insertions(+), 25 deletions(-) diff --git a/frontend/src/App.svelte b/frontend/src/App.svelte index 0cd7a96..e0fb840 100644 --- a/frontend/src/App.svelte +++ b/frontend/src/App.svelte @@ -21,7 +21,6 @@ import { CheckIfMALLoggedInAndSetUser } from "./helperModules/CheckIfMyAnimeListLoggedIn.svelte"; import { CheckIfSimklLoggedInAndSetUser } from "./helperModules/CheckIsSimklLoggedIn.svelte"; import {App} from "../bindings/AniTrack"; - import { router } from "svelte-spa-router"; import ErrorModal from "./helperComponents/ErrorModal.svelte"; onMount(async () => { @@ -39,26 +38,35 @@ !isSimklLoggedIn && (await CheckIfSimklLoggedInAndSetUser()); }); - $: if (router.location === "/" && $watchlistNeedsRefresh) { - (async () => { - if ($aniListLoggedIn && $aniListPrimary) { - await CheckIfAniListLoggedInAndLoadWatchList(); - } - if ($malLoggedIn && $malPrimary) { - await App.GetMyAnimeList(1000).then((w) => malWatchList.set(w)); - } - if ($simklLoggedIn && $simklPrimary) { - await App.SimklGetUserWatchlist().then((w) => simklWatchList.set(w)); - } + import { get } from "svelte/store"; - watchlistNeedsRefresh.set(false); - })(); + // Reloads all watchlists when returning home with the refresh flag set + // (e.g. after saving changes on a detail page). Driven by the router's + // onRouteLoaded callback: the old `$: ... router.location ...` reactive + // statement did not reliably re-fire on navigation under Svelte 5, + // so homecoming refreshes silently never ran. + async function refreshHomeWatchlists(location: string): Promise { + if (location !== "/" || !get(watchlistNeedsRefresh)) return; + if (get(aniListLoggedIn) && get(aniListPrimary)) { + await CheckIfAniListLoggedInAndLoadWatchList(); + } + if (get(malLoggedIn) && get(malPrimary)) { + await App.GetMyAnimeList(1000).then((w) => malWatchList.set(w)); + } + if (get(simklLoggedIn) && get(simklPrimary)) { + await App.SimklGetUserWatchlist().then((w) => simklWatchList.set(w)); + } + + watchlistNeedsRefresh.set(false); }
{ + void refreshHomeWatchlists(detail.location); + }} routes={{ "/": Home, "/anime/:id": wrap({ diff --git a/frontend/src/helperFunctions/loader.ts b/frontend/src/helperFunctions/loader.ts index 5bb97fb..19a0596 100644 --- a/frontend/src/helperFunctions/loader.ts +++ b/frontend/src/helperFunctions/loader.ts @@ -1,18 +1,29 @@ +import { mount, unmount } from 'svelte'; import Spinner from '../helperComponents/Spinner.svelte'; export default (node: any, loading: any) => { - let Spin: any - loading.subscribe((loading: any) => { - if(loading){ - Spin = new Spinner({ + let app: any; + const unsubscribe = loading.subscribe((isLoading: any) => { + if (isLoading && !app) { + app = mount(Spinner, { target: node, intro: true - }) - } else { - if(Spin){ - Spin?.$destroy?.() - Spin = undefined; + }); + } else if (!isLoading && app) { + const current = app; + app = undefined; + // Teardown must never break the caller. + void unmount(current).catch(() => {}); + } + }); + return { + destroy() { + unsubscribe(); + if (app) { + const current = app; + app = undefined; + void unmount(current).catch(() => {}); } } - }) -} \ No newline at end of file + }; +}