# 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<ChatConnection | null>(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<ChatConnection | null>(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.
