Nystart basert på arkitektonisk innsikt fra Sidelinja v1. Koden er ny, visjon og primitiver er validert gjennom tidligere arbeid. Inneholder: - Komplett arkitekturdokumentasjon (docs/arkitektur.md) - 6 vedtatte retninger (docs/retninger/) - Alle concepts, features, proposals og erfaringer fra v1 - Server-oppsett og drift (docs/setup/) - LiteLLM-konfigurasjon (API-nøkler via env) - Editor.svelte referanse fra v1 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
73 lines
2.5 KiB
Markdown
73 lines
2.5 KiB
Markdown
# 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.
|