The layout's play-intent effect gated audioEl.play() on player.isPlaying
(state === 'playing'), but the state progression is loading → DOM play()
→ 'playing' event → state='playing'. Without calling play() during
loading, the DOM never starts, the event never fires, and state is
pinned to loading forever. Call play() for the intent-to-play union
(loading + playing), matching the store's design.
Single hidden <audio> survives navigation. Effects drive it from the
store (src, volume, play/pause) and DOM events report back (timeupdate,
loadedmetadata, playing, pause, waiting, ended, error). useMediaSession
is called once here so OS media controls light up as soon as the user
signs in.
+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.
- SvelteKit 2.x + Svelte 5 + TypeScript + Vite 5
- adapter-static with fallback:'index.html' for SPA deep-link routing
- Tailwind configured with dark-only palette matching spec
- Placeholder landing page at /
- Committed web/build/index.html placeholder (via .gitignore negation) so
go:embed works before the SvelteKit build has been run