feat(web): wire root layout — bootstrap, guard, Shell, QueryProvider

+layout.ts runs auth.bootstrap() in load() so the shell sees the user
synchronously. +layout.svelte installs the TanStack QueryClientProvider,
runs the route guard as a \$effect, and swaps Shell vs. bare slot
based on auth state.
This commit is contained in:
2026-04-23 06:11:43 -04:00
parent fe410f14eb
commit 048663a4c8
2 changed files with 37 additions and 1 deletions
+27 -1
View File
@@ -1,5 +1,31 @@
<script lang="ts">
import '../app.css';
import { page } from '$app/state';
import { goto } from '$app/navigation';
import { QueryClientProvider } from '@tanstack/svelte-query';
import { queryClient } from '$lib/query/client';
import { user } from '$lib/auth/store.svelte';
import Shell from '$lib/components/Shell.svelte';
let { children } = $props<{ children: import('svelte').Snippet }>();
// Reactive guard: runs every time page.url or user.value changes.
$effect(() => {
const path = page.url.pathname;
const isLogin = path === '/login';
if (user.value === null && !isLogin) {
const returnTo = path + page.url.search;
goto('/login?returnTo=' + encodeURIComponent(returnTo), { replaceState: true });
} else if (user.value !== null && isLogin) {
goto('/', { replaceState: true });
}
});
</script>
<slot />
<QueryClientProvider client={queryClient}>
{#if user.value !== null && page.url.pathname !== '/login'}
<Shell>{@render children()}</Shell>
{:else}
{@render children()}
{/if}
</QueryClientProvider>
+10
View File
@@ -0,0 +1,10 @@
import type { LayoutLoad } from './$types';
import { bootstrap } from '$lib/auth/store.svelte';
export const ssr = false; // adapter-static fallback; we're SPA-only
export const prerender = false;
export const load: LayoutLoad = async () => {
await bootstrap();
return {};
};