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
This commit is contained in:
John O'Keefe
2026-09-16 16:30:08 -04:00
parent e8a5768a2f
commit 515678f035
2 changed files with 44 additions and 25 deletions
+22 -14
View File
@@ -21,7 +21,6 @@
import { CheckIfMALLoggedInAndSetUser } from "./helperModules/CheckIfMyAnimeListLoggedIn.svelte"; import { CheckIfMALLoggedInAndSetUser } from "./helperModules/CheckIfMyAnimeListLoggedIn.svelte";
import { CheckIfSimklLoggedInAndSetUser } from "./helperModules/CheckIsSimklLoggedIn.svelte"; import { CheckIfSimklLoggedInAndSetUser } from "./helperModules/CheckIsSimklLoggedIn.svelte";
import {App} from "../bindings/AniTrack"; import {App} from "../bindings/AniTrack";
import { router } from "svelte-spa-router";
import ErrorModal from "./helperComponents/ErrorModal.svelte"; import ErrorModal from "./helperComponents/ErrorModal.svelte";
onMount(async () => { onMount(async () => {
@@ -39,26 +38,35 @@
!isSimklLoggedIn && (await CheckIfSimklLoggedInAndSetUser()); !isSimklLoggedIn && (await CheckIfSimklLoggedInAndSetUser());
}); });
$: if (router.location === "/" && $watchlistNeedsRefresh) { import { get } from "svelte/store";
(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));
}
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<void> {
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);
} }
</script> </script>
<Header /> <Header />
<ErrorModal /> <ErrorModal />
<Router <Router
onRouteLoaded={(detail) => {
void refreshHomeWatchlists(detail.location);
}}
routes={{ routes={{
"/": Home, "/": Home,
"/anime/:id": wrap({ "/anime/:id": wrap({
+22 -11
View File
@@ -1,18 +1,29 @@
import { mount, unmount } from 'svelte';
import Spinner from '../helperComponents/Spinner.svelte'; import Spinner from '../helperComponents/Spinner.svelte';
export default (node: any, loading: any) => { export default (node: any, loading: any) => {
let Spin: any let app: any;
loading.subscribe((loading: any) => { const unsubscribe = loading.subscribe((isLoading: any) => {
if(loading){ if (isLoading && !app) {
Spin = new Spinner({ app = mount(Spinner, {
target: node, target: node,
intro: true intro: true
}) });
} else { } else if (!isLoading && app) {
if(Spin){ const current = app;
Spin?.$destroy?.() app = undefined;
Spin = 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(() => {});
} }
} }
}) };
} }