# Erfaring: Svelte 5 Reaktivitet ## 1. `$state` i `.svelte.ts` krever getters Svelte 5 sin `$state` lager reaktive proxyer. Når en funksjon returnerer et objekt med `$state`-verdier, **mister man reaktiviteten** hvis man returnerer verdien direkte: ```typescript // FEIL — mister reaktivitet, verdien fryses ved retur function createThing() { let count = $state(0); return { count }; // snapshot, ikke reaktiv } // RIKTIG — getter bevarer proxy-tilgang function createThing() { let count = $state(0); return { get count() { return count; } }; } ``` **Referanse:** `web/src/lib/chat/pg.svelte.ts` — alle returnerte verdier bruker getters. ## 2. SSR kjører alt utenfor `onMount` SvelteKit server-renderer kjører komponent-script ved SSR. Alt som bruker browser-APIer (WebSocket, `fetch` til relative URLer, `setInterval`, `sessionStorage`) **krasjer på serveren** hvis det ikke er beskyttet. ```typescript // FEIL — krasjer ved SSR let chat = createChat(channelId); // kjøres på server // RIKTIG — kun i browser import { onMount } from 'svelte'; let chat = $state(null); onMount(() => { chat = createChat(channelId); return () => chat?.destroy(); }); ``` Alternativt kan factory-funksjonen selv sjekke: ```typescript import { browser } from '$app/environment'; if (browser) { /* ... */ } ``` **Referanse:** `web/src/lib/chat/create.svelte.ts` — `browser`-guard i factory, `web/src/lib/blocks/ChatBlock.svelte` — `onMount` for opprettelse. ## 3. `$derived` og `$effect` med null-initialisert state Når en `$state`-variabel starter som `null` (fordi den settes i `onMount`), må `$derived` og `$effect` håndtere null-tilfellet: ```typescript let chat = $state(null); let messages = $derived(chat?.messages ?? []); // fallback til tom liste $effect(() => { const count = messages.length; // trygt, alltid array if (count > prevCount) scrollToBottom(); prevCount = count; }); ``` **Referanse:** `web/src/lib/blocks/ChatBlock.svelte` — `$derived` med optional chaining. ## 4. Polling: full-fetch slår inkrementell akkumulering Vi prøvde først å bruke en `latestTimestamp`-cursor for å hente kun nye meldinger og appende dem. Dette ga duplikater — telleren vokste mens man tastet (polling i bakgrunnen). **Løsning:** Enkel `refresh()` som alltid henter full liste og erstatter `messages` i sin helhet. For et lite volum meldinger er dette enklere og tryggere enn inkrementell logikk. **Referanse:** `web/src/lib/chat/pg.svelte.ts` — `refresh()` gjør full fetch, ingen akkumulering.