Compare commits

...

2 commits

Author SHA1 Message Date
6ec430141b Oppdater scripts/summary.sh med retninger/ og ops/
Retninger kommer tidlig i dokumentet (etter arkitektur og CLAUDE.md)
for å gi overblikk først ved bruk som bakgrunnsprompt for LLM-er.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 04:54:24 +01:00
8ca9832248 Legg til ops/ (vedlikeholdsjobber) og docs/retninger/ (arkitektoniske teser)
Ny mappe ops/ med repeterbare vedlikeholdsjobber:
- ryddejobb.md — full prosjektrevisjon
- doc-audit.md — docs vs kode
- drift-sjekk.md — prod vs lokal vs docs

Ny mappe docs/retninger/ med arkitektoniske teser:
- status_quo.md — hva Sidelinja er i dag
- rom_ikke_forum.md — opplevelse-først, to-lags-modell, administrativ opplevelse
- universell_input.md — tre primitiver (input, mottak, kommunikasjon), noder+edges
- maskinrommet.md — Rust-orkestrator, edge-drevet ressursorkestrering, CAS+pruning
- bruker_ikke_workspace.md — brukeren er sentrum, workspaces er samlings-noder
- datalaget.md — PG+Apache AGE, SpacetimeDB som sanntidslag, lagmodell

Oppdatert CLAUDE.md og proposals/README.md med referanser.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 04:54:17 +01:00
14 changed files with 1463 additions and 2 deletions

View file

@ -51,6 +51,13 @@ CLAUDE.md er eneste startdokument. Alt annet ligger under `docs/`:
- `synkronisering.md` — PostgreSQL ↔ SpacetimeDB dataflyt og eierskapsmodell - `synkronisering.md` — PostgreSQL ↔ SpacetimeDB dataflyt og eierskapsmodell
- `api_grensesnitt.md` — Kommunikasjonskart: SvelteKit er web-API, Rust er worker - `api_grensesnitt.md` — Kommunikasjonskart: SvelteKit er web-API, Rust er worker
- `ai_gateway.md` — LiteLLM som sentralisert AI-ruter (BYOK + OpenRouter fallback) - `ai_gateway.md` — LiteLLM som sentralisert AI-ruter (BYOK + OpenRouter fallback)
- `docs/retninger/` — Store arkitektoniske teser og retningsspørsmål (se `README.md` for oversikt):
- `status_quo.md` — Hva Sidelinja er i dag: ambisiøse primitiver, tradisjonell overflate
- `rom_ikke_forum.md` — Bør Sidelinja være et sanntidsrom fremfor en tradisjonell webapp?
- `universell_input.md` — Én multimodal input/mottak-primitiv, noder + edges i stedet for separate tabeller
- `maskinrommet.md` — Rust-tjenestelaget: fang, prosesser, lever — fast grensesnitt for alle tekniske tjenester
- `bruker_ikke_workspace.md` — Brukeren er sentrum, workspaces er frivillige samlings-noder
- `datalaget.md` — PG + Apache AGE som enhetlig graf og arkiv, migreringsstrategi
- `docs/proposals/` — Halvtenkte idéer og kreative innfall (se `README.md` for oversikt) - `docs/proposals/` — Halvtenkte idéer og kreative innfall (se `README.md` for oversikt)
- `docs/erfaringer/` — Lærdommer fra implementering (feller, anti-patterns, løsninger): - `docs/erfaringer/` — Lærdommer fra implementering (feller, anti-patterns, løsninger):
- `svelte5_reaktivitet.md` — $state-getters, SSR-feller, polling-mønster - `svelte5_reaktivitet.md` — $state-getters, SSR-feller, polling-mønster
@ -81,7 +88,7 @@ CLAUDE.md er eneste startdokument. Alt annet ligger under `docs/`:
- Tunge AI-jobber (Whisper, LLM-kall) skal aldri blokkere web-requests - Tunge AI-jobber (Whisper, LLM-kall) skal aldri blokkere web-requests
- All AI-kode peker på `http://ai-gateway:4000/v1` — aldri direkte til leverandør-APIer - All AI-kode peker på `http://ai-gateway:4000/v1` — aldri direkte til leverandør-APIer
- Kod og test lokalt i WSL2, deploy via push til Forgejo + SSH pull - Kod og test lokalt i WSL2, deploy via push til Forgejo + SSH pull
- Sjekk alltid relevant doc i `docs/concepts/`, `docs/features/` eller `docs/infra/` før du implementerer - Sjekk alltid relevant doc i `docs/concepts/`, `docs/features/`, `docs/infra/` eller `docs/retninger/` før du implementerer
- Sjekk `docs/erfaringer/` for kjente feller før du implementerer med Svelte 5, SpacetimeDB eller adapter-mønsteret - Sjekk `docs/erfaringer/` for kjente feller før du implementerer med Svelte 5, SpacetimeDB eller adapter-mønsteret
- Etter ferdig implementering av en komponent: dokumenter lærdommer i `docs/erfaringer/` - Etter ferdig implementering av en komponent: dokumenter lærdommer i `docs/erfaringer/`

View file

@ -5,11 +5,12 @@ Halvtenkte idéer, kreative innfall og ting vi vil utforske når vi får tid. Ik
## Pipeline ## Pipeline
``` ```
retninger/ → påvirker alt (arkitektoniske teser)
proposals/ → features/ eller concepts/ proposals/ → features/ eller concepts/
(idé) (spesifisert, klar for implementering) (idé) (spesifisert, klar for implementering)
``` ```
Når en idé modnes nok til å bli implementert, skrives en full spec i `docs/features/` eller `docs/concepts/` og forslaget slettes herfra. Når en idé modnes nok til å bli implementert, skrives en full spec i `docs/features/` eller `docs/concepts/` og forslaget slettes herfra. Idéer som er for store og fundamentale for proposals — arkitektoniske teser om prosjektets retning — hører hjemme i `docs/retninger/`.
## Oversikt ## Oversikt

36
docs/retninger/README.md Normal file
View file

@ -0,0 +1,36 @@
# Retninger
Store, åpne spørsmål om prosjektets identitet og arkitektoniske retning.
Dette er ikke features, ikke proposals, ikke spesifikasjoner — det er **teser** som
utforsker hvordan Sidelinja bør tenke om seg selv. En retning kan påvirke alt fra
teknologivalg til UX-filosofi, men den er ikke en beslutning. Den er en pågående
diskusjon.
## Pipeline
```
retninger/ → kan informere alt:
(tese) concepts/, features/, infra/, arkitektur.md
```
En retning "forfremmes" ikke — den modnes, og det den konkluderer med påvirker
andre dokumenter. En retning kan også forkastes eller parkeres.
## Oversikt
| Retning | Status | Kjernespørsmål |
|---------|--------|----------------|
| [Status quo](status_quo.md) | Referanse | Hva er Sidelinja i dag? Ankerpunkt for de andre retningene. |
| [Rom, ikke forum](rom_ikke_forum.md) | Åpen | Bør Sidelinja være en oppslukende sanntidsopplevelse fremfor en tradisjonell webapp? |
| [Universell input og mottak](universell_input.md) | Åpen | Én multimodal input-primitiv + personlig mottaksflate, noder + edges i stedet for separate tabeller |
| [Maskinrommet](maskinrommet.md) | Åpen | Én Rust-tjeneste med fast grensesnitt for alle tekniske tjenester: fang, prosesser, lever |
| [Bruker, ikke workspace](bruker_ikke_workspace.md) | Åpen | Brukeren er sentrum, workspaces er frivillige samlings-noder — ikke containere |
| [Datalaget](datalaget.md) | Åpen | PG + Apache AGE som enhetlig graf og arkiv, SpacetimeDB som sanntidslag, CAS for binærdata |
## Format
- Hva er tesen?
- Hva motiverer den? (observasjoner, frustrasjoner, inspirasjon)
- Hva ville vært annerledes hvis vi fulgte den?
- Spenninger og åpne spørsmål
- Ingen krav om konklusjon

View file

@ -0,0 +1,127 @@
# Bruker, ikke workspace
> Primærenheten er brukeren og brukerens edges. Workspaces er ikke
> containere — de er frivillige samlings-noder som gir felles kontekst.
## Observasjoner
I dag er workspaces den organisatoriske enheten. Du "er i" et workspace,
og det bestemmer hva du ser: kanaler, boards, kalendre. Vil du se noe
fra et annet workspace må du bytte. Det er en container-modell — ting
*bor* i workspaces.
Men i node+edge-modellen gir dette ikke mening. En node er ikke "i" noe
— den har edges til ting. Og brukeren er ikke "i" et workspace — brukeren
har edges til noder, personer, topics, samtaler.
## Tesen
**Brukeren er sentrum.** Du logger inn og ser dine edges — alt du er
koblet til. Ikke "velg workspace" som første handling, men "her er alt
ditt."
### Workspace som samlings-node, ikke container
Et workspace eksisterer fortsatt som konsept, men det er en node i
grafen — ikke en organisatorisk boks:
- **Tradisjonell modell:** Workspace inneholder kanaler, boards, filer.
Brukeren er "i" workspacet.
- **Node-modell:** Workspace er en node. Noder har edge til den. Brukeren
har edge til den. Workspace-noden bærer felles kontekst (tema,
pruning-profil, AI-konfig).
En node kan ha edge til flere workspaces. En faktoid om en gjest er
relevant for både podcast-prosjektet og research-samlingen. Ikke kopi —
samme node, to edges.
### Personlig er default
Alt uten workspace-edge eller mottaker-edge er privat — tilhører bare
deg. "Personlig workspace" er ikke en egen ting du oppretter. Det er
fravær av deling. Dine private notater, dagbok, voice memos — de har
bare edges til deg.
### Administrasjon er edges med roller
Brukerstyring forvaltes i nodene selv, ikke i et sentralt admin-panel:
| Edge-type | Hva den gir |
|-----------|------------|
| Eier-edge | Full kontroll — slette, endre tilgang, endre innstillinger |
| Admin-edge | Kan invitere/fjerne andre, endre konfigurasjon |
| Deltaker-edge | Kan gi input og motta |
| Leser-edge | Kan kun motta (observatør, lytter) |
Du oppretter en kommunikasjonsnode (podcast-prosjekt). Du er eier
automatisk. Du inviterer Trond → deltaker-edge. Trond kan gi input
men ikke slette eller endre tilgang. Du gir Trond admin-edge → nå
kan han invitere andre og endre innstillinger på den noden.
Dette skalerer fra en privat notat (kun eier-edge til deg) til en
organisasjon (samlings-node med mange admin- og deltaker-edges).
### Brukeropplevelsen
Når du logger inn ser du:
- **Dine aktive samtaler** — kommunikasjonsnoder med edge til deg
- **Dine noder** — alt du har skapt eller er koblet til
- **Dine samlings-noder** (det som før var workspaces) — grupperer
kontekst, men du trenger ikke "gå inn i" dem
- **Din mottaksflate** — alt som er relevant for deg nå, vektet
Du kan filtrere etter samlings-node hvis du vil fokusere ("vis bare
podcast-prosjektet"), men det er et filter — ikke en modebytte.
### Felles kontekst på samlings-noder
En samlings-node (workspace) bærer kontekst som arves av tilknyttede
noder:
- **Pruning-profil** — hvor aggressivt slettes binærdata?
- **Tema** — visuelt uttrykk (CSS custom properties)
- **AI-konfigurasjon** — hvilke prompts, hvilken modell, hvilke regler
- **Tilgangsnivå** — default synlighet for nye noder opprettet i
denne konteksten
- **Kapasitet** — ressursgrenser for maskinrommet (f.eks. maks
samtidige transkripsjoner)
## Implikasjoner
### Kryssgående noder er naturlig
En node med edge til to samlings-noder arver kontekst fra begge.
Konflikter (ulik pruning-profil) løses med prioriteringsregler:
mest konservativ vinner, eller eier-edge bestemmer.
### Onboarding forenkles
Ny bruker trenger ikke "settes opp i et workspace." De får edges til
de nodene de trenger tilgang til. Ferdig. Endre tilgang = endre edges.
### Forlate et prosjekt er å fjerne edges
Ingen "slett bruker fra workspace." Fjern deltaker-edges. Brukerens
private noder som hadde edge til samlings-noden beholder den edgen
(det er brukerens innhold), men de mister tilgang til andres noder.
## Spenninger og åpne spørsmål
- **Overblikk.** Uten workspaces som organisatorisk enhet — hvordan
unngår du at alt flyter sammen? Samlings-noder er svaret, men de
må være intuitive å opprette og bruke uten å bli "workspaces med
ny navn."
- **Tilgangskontroll i praksis.** Edge-basert tilgang er fleksibelt,
men kan det bli uoversiktlig? "Hvem har tilgang til hva" må være
lett å besvare. Kanskje samlings-noden gir en naturlig audit-visning.
- **Arv og konflikter.** En node med edge til to samlings-noder med
ulike pruning-profiler — hva vinner? Trenger klare regler som er
intuitive for brukeren.
- **Migrering.** Eksisterende workspace-modell har innhold. Kan
workspaces bli samlings-noder gradvis?
## Forhold til andre retninger
- [Rom, ikke forum](rom_ikke_forum.md) — "rommet" er ikke et workspace,
det er summen av dine edges
- [Universell input og mottak](universell_input.md) — mottaksflaten er
"noder med edge til *meg*", ikke "noder i *mitt workspace*"
- [Maskinrommet](maskinrommet.md) — leser samlings-node-edges for
kontekst (pruning, kapasitet, AI-konfig)

175
docs/retninger/datalaget.md Normal file
View file

@ -0,0 +1,175 @@
# Datalaget — PG + AGE som enhetlig graf og arkiv
> PostgreSQL er den eneste databasen. Apache AGE gir Cypher-semantikk
> for graftraverseringer. SpacetimeDB er sanntidslaget over samme data.
> Én database, to tilgangsmønstre, ingen eierskapskonflikt.
## Observasjoner
Hele retningsarbeidet har bygget seg mot "alt er noder og edges."
Tilgang er edges, visninger er spørringer, administrasjon er
traversering. Spørsmålet er: hvilken teknologi støtter dette best?
### Neo4j ble vurdert og forkastet
Neo4j er best-in-class for dype graftraverseringer, men for dette
prosjektet gir den mer problemer enn den løser:
- **Tredje database.** PG + SpacetimeDB + Neo4j = tre systemer å
drifte, sikkerhetskopiere og overvåke. På én Hetzner VPS.
- **Mister PG-økosystemet.** pgvector (semantisk søk), fulltekstsøk,
JSONB, pg_cron, statistikk-aggregeringer — alt dette trenger vi
uansett, og det lever i PG. Med Neo4j trenger vi PG *i tillegg*.
- **Minnehungrig.** Neo4j anbefaler 8-16GB heap. På en delt VPS med
PG, SpacetimeDB, Whisper og LiteLLM er det for mye.
- **Lisens.** Community er AGPLv3. Enterprise-features (clustering,
avansert sikkerhet) krever betalt lisens.
### Apache AGE — Cypher i PG
Apache AGE er en PG-extension som legger til openCypher-støtte. Noder
og edges lagres i PG-tabeller, men spørres med Cypher-semantikk.
**Oppsider:**
- **Én database.** Ingen ny infrastruktur. Samme backup, connection
pooling, tooling, monitoring.
- **SQL + Cypher i samme spørring.** "Finn alle noder brukeren har
tilgang til via team-traversering, JOINet med fulltekstsøk og
pgvector." Det kan du ikke gjøre med en separat grafdatabase.
- **Null migrasjonskostnad.** Eksisterende nodes/edges-tabeller kan
eksponeres som graf. Legg til extension, ikke bytt database.
- **Økosystemet bevares.** pgvector, fulltekstsøk, JSONB, pg_cron —
alt fungerer side om side med Cypher-spørringer.
- **Produksjonsbruk.** Azure og EDB tilbyr AGE som managed extension.
**Nedsider:**
- **Ikke native graf.** Under panseret er det PG-tabeller. For svært
dype traverseringer (10+ hopp over millioner av noder) er Neo4j
raskere. Men de fleste spørringene våre er 1-5 hopp.
- **Yngre prosjekt.** Mindre community, tynnere dokumentasjon enn Neo4j.
- **Quirks.** Spørringer må være enten read-only eller write-only
(WITH-clause for overgang). Noen pg_upgrade-begrensninger.
## Beslutning
**PostgreSQL + Apache AGE** som enhetlig datalager for noder og edges.
De fleste spørringer i dette systemet er grunne:
- "Vis noder med edge til denne brukeren" — 1 hopp
- "Sjekk tilgang via team" — 2 hopp
- "Finn alle kommunikasjonsnoder brukeren har tilgang til" — 2-3 hopp
- "Traverser kunnskapsgrafen mellom to emner" — 3-5 hopp
AGE håndterer dette. Skulle det vise seg at dypere traverseringer
blir en flaskehals *i praksis*, kan Neo4j evalueres da — men med
én VPS og realistiske datamengder er det usannsynlig.
## Lagmodell
```
┌─────────────────────────────────────┐
│ GUI (SvelteKit) │
│ Primitiver, visninger │
└────────┬──────────────────┬─────────┘
│ skriv │ les (sanntid)
│ │ direkte WebSocket
┌────────▼────────┐ ┌─────▼─────────┐
│ Maskinrommet │ │ SpacetimeDB │──┐
│ (Rust) │ │ (STDB) │ │
│ Orkestrering │ └───────────────┘ │
└──┬─────┬─────┬──┘ │
│ │ │ sync ↕ │
▼ ▼ ▼ │
┌─────┐┌─────┐┌─────┐┌─────────────┐ │
│ PG ││STDB ││ CAS ││ Whisper, │ │
│+AGE ││(skr)││ ││ LiteLLM, │ │
│ ││ ││ ││ LiveKit ... │ │
└─────┘└─────┘└─────┘└─────────────┘ │
```
### Skriv vs les — to stier med god grunn
**Skriv:** GUI → Maskinrommet (Rust) → tjenester (PG, STDB, CAS, ...)
All orkestrering, edge-logikk, ressursallokering og validering går
gjennom Rust. Maskinrommet bestemmer hva som skjer: hva som skrives
til PG, hva som speiles til SpacetimeDB, hva som lagres i CAS,
hvilke tjenester som trigges.
**Les (sanntid):** SpacetimeDB → GUI (direkte WebSocket)
SpacetimeDB sitt klient-SDK kobler seg direkte via WebSocket,
definerer SQL-subscriptions, og synkroniserer automatisk med lokal
cache — uten network round-trips for lesing (~10μs per transaksjon).
Å proxy dette gjennom Rust ville lagt til et hopp, serialisering og
kontekstbytte — en dårligere reimplementering av noe STDB gjør
optimalt. Den direkte lese-stien er en bevisst arkitekturbeslutning,
ikke et hull i lagmodellen.
**Les (tradisjonelt):** GUI → Maskinrommet (Rust) → PG
Søk, historikk, statistikk, arkiv — alt som ikke er sanntid går
gjennom Rust og PG. AGE-spørringer for graftraversering, pgvector
for semantisk søk, SQL for aggregeringer.
### PG er arkivet og grafen
Alle noder og edges lever i PG. AGE gir Cypher-spørringer for
traversering. Standard SQL for alt annet (aggregeringer, fulltekstsøk,
JOINs mot pgvector). Én database, to spørrespråk som utfyller
hverandre.
### SpacetimeDB er sanntidslaget
Aktive noder og edges speilet til SpacetimeDB for live-oppdateringer.
Ting som er "nå" lever i SpacetimeDB. Ting som er "ferdig" lever kun
i PG. Ingen eierskapskonflikt — SpacetimeDB er en live-visning av
en delmengde av PG-grafen.
### CAS er binærlageret
Lyd, bilde, video lagres content-addressable utenfor PG. Noder i
PG peker på CAS-hasher. Pruning-regler basert på edges, aksesslog
og modalitet (se maskinrommet).
## Migreringsstrategi
### Fase 1: AGE på eksisterende PG
Installer Apache AGE extension. Eksponer eksisterende nodes/edges-
tabeller som graf. Ingen endring for resten av systemet — bare en
ny måte å spørre på.
### Fase 2: Konsolider til node+edge-modellen
Migrer meldingsboks, kanban, kalender etc. til den universelle
node+edge-modellen gradvis. Gamle tabeller kan leve som views
over grafen i overgangsperioden.
### Fase 3: Parallell prototype
Det gamle systemet kjører uforstyrret. En ny frontend-prototype
bygges ved siden av som bruker AGE-grafen direkte. Live sync fra
gammelt til nytt via enveis oppdateringer. Testbrukere leker i
det nye, produksjon er trygt i det gamle.
### Fase 4: Cutover
Når det nye er godt nok, flyttes trafikk over. Det gamle systemet
kan kjøre som read-only fallback en periode.
## Spenninger og åpne spørsmål
- **AGE-modenhet.** Prosjektet er aktivt og støttet av Azure/EDB,
men community er mindre enn Neo4j. Risikoen er håndterbar fordi
dataene alltid er PG-tabeller — AGE er bare et spørrelag.
- **SpacetimeDB-synk.** Hvordan synkes noder/edges mellom PG (AGE)
og SpacetimeDB effektivt? Trenger en sync-mekanisme som forstår
"aktiv delmengde."
- **Skjemadesign.** Én node-tabell med alle typer innhold — hva er
felles kolonner vs edge-metadata vs JSONB-payload? Trenger
gjennomtenkt skjema som balanserer fleksibilitet og spørrbarhet.
## Forhold til andre retninger
- [Universell input og mottak](universell_input.md) — noder og edges
er den underliggende datamodellen for alle tre primitiver
- [Maskinrommet](maskinrommet.md) — CAS og pruning lever her, AGE
brukes for edge-drevet ressursorkestrering
- [Bruker, ikke workspace](bruker_ikke_workspace.md) — tilgang via
graf-traversering (AGE) i stedet for workspace-membership-tabeller
- [Rom, ikke forum](rom_ikke_forum.md) — to-lags-modellen:
SpacetimeDB for nåtid, PG+AGE for arkiv og graf

View file

@ -0,0 +1,301 @@
# Maskinrommet — teknisk tjenestelaget
> Én Rust-tjeneste med et fast grensesnitt. Alle tekniske tjenester beveger
> seg gjennom dette laget. Fang, prosesser, lever.
## Observasjoner
I dag er tekniske tjenester spredt:
- **Worker** (Rust) — kjører bakgrunnsjobber
- **Jobbkø** (PG) — koordinerer arbeid
- **AI Gateway** (LiteLLM) — ruter AI-kall
- **Whisper** — transkripsjon
- **LiveKit** — lyd/video-strømmer
Hver har sitt eget grensesnitt. Frontend og primitiv-laget må vite hva
som finnes under panseret. Det er ingen felles abstraksjon, ingen felles
logging, ingen felles kapasitetsstyring.
## Tesen
Alt som krever tunge ressurser eller eksterne tjenester går gjennom
**ett lag** med **ett grensesnitt**. Ikke fordi det er elegant — fordi
det gir et fast punkt som er enkelt å fange, modifisere og forbedre.
Maskinrommet gjør tre ting:
### 1. Fang (input-absorpsjon)
Ta imot råmateriale i alle modaliteter:
- Tekst (melding, URL, dokument)
- Lyd (voice memo, live stream, filopplasting)
- Bilde (foto, skjermbilde, tegning)
- Video (stream, opptak)
- Strukturert data (JSON, metadata, edges)
### 2. Prosesser (transformasjon)
Analyser, transformer, berik og systematiser:
- **STT** — lyd → tekst (Whisper)
- **TTS** — tekst → lyd (ElevenLabs / lokal modell)
- **AI-analyse** — oppsummering, klassifisering, sentimentanalyse,
faktasjekk, edge-forslag
- **Beriking** — URL → metadata, bilde → beskrivelse, lyd → segmenter
- **Søk** — fulltekst, semantisk (pgvector), graftraversering
- **Mediaprosessering** — transcode, thumbnail, waveform
### 3. Lever (output-distribusjon)
Lever resultat i riktig modalitet til riktig mottaker:
- Tekst (melding, notifikasjon, digest)
- Lyd (TTS-opplesning, lydstream)
- Video/bilde (stream, thumbnail, snapshot)
- Strukturert data (noder, edges, metadata tilbake i grafen)
- Push (webhook, SSE, SpacetimeDB-reducer)
## Edge-drevet ressursorkestrering
Nøkkelinnsikten: **maskinrommet leser edges for å vite hva det skal gjøre.**
Noden selv er alltid enkel. Det er edgene som bestemmer hvilke ressurser
som spinnes opp.
### Security by default
Input uten mottaker-edge er automatisk privat. Du trenger ikke "velge
privat" — det er utgangspunktet. Ingen ser det. Ingen ressurser kobles
inn utover det grunnleggende (fang + transkriber). Privat er ikke en
innstilling, det er fravær av deling.
### Ressurser er proporsjonale med edges
Samme nodetype, vilt forskjellig ressursbruk:
```
Dagboknotat (privat voice memo):
node → fang lyd → transkriber (Whisper) → lagre
Ressurser: minimal
Samtale med Trond:
node + mottaker-edge(Trond)
→ fang lyd → transkriber → lever tekst/lyd til Trond
Ressurser: STT + levering til én
Redaksjonsmøte (5 deltakere):
node + mottaker-edges(5) + rolle-edges
→ fang lyd fra alle → transkriber → lever til alle → AI-referent
Ressurser: STT + levering til 5 + LLM
Livesending (1000 lyttere):
node + mottaker-edges(∞) + stream-edge + publiserings-edge
→ fang lyd → transkriber → stream via LiveKit → distribuer
→ generer segmenter → kjør live AI → publiser
Ressurser: STT + LiveKit + LLM + mediaprosessering
```
Maskinrommet gjør ikke mer enn det edges krever. Ingen overhead for
enkle ting. Noden vet ingenting om LiveKit — den har bare edges som
sier "stream til disse mottakerne", og maskinrommet bestemmer at det
betyr LiveKit.
### Naturlig eskalering
Du starter en privat voice-note. Bestemmer deg for å dele den med Trond
→ legg til mottaker-edge, maskinrommet begynner å levere. Trond foreslår
at dere tar det som et møte → legg til flere deltaker-edges, maskinrommet
kobler inn sanntidsstrømming. Møtet blir en innspilling → legg til
publiserings-edge, maskinrommet aktiverer produksjonspipeline.
Hvert steg er bare å legge til edges. Maskinrommet reagerer og kobler
inn flere ressurser etter hvert. Ingen migrering, ingen modebytte.
## Grensesnittet
Maskinrommet eksponerer et konsistent API — sannsynligvis en Rust trait
eller et sett traits:
```
fang(input: RåInput) → NodeId
prosesser(node: NodeId, operasjon: Operasjon) → Resultat
lever(node: NodeId, mottaker: Mottaker, format: Format) → Status
```
Men i praksis er mye av dette *reaktivt*: maskinrommet observerer
edge-endringer og handler automatisk. Legger noen til en mottaker-edge
→ maskinrommet begynner å levere. Legger noen til en stream-edge →
maskinrommet kobler inn LiveKit. Primitivene trenger ikke eksplisitt
kalle `lever()` — de manipulerer edges, og maskinrommet reagerer.
## Hva dette gir
### Isolasjon
Bytt Whisper med noe annet? Endre maskinrommet. Frontend vet ingenting.
Legg til bildegenerering? Ny operasjon i maskinrommet. Primitivene
kaller den uten å vite hva som skjer under.
### Observerbarhet
Alt går gjennom ett punkt. Logging, metrikker, kostnadsrapportering,
feilhåndtering — alt på ett sted. "Hva bruker vi AI-ressurser på?"
har ett svar.
### Kapasitetsstyring
Prioritering, kø, rate limiting, fallback mellom leverandører — alt
håndtert av maskinrommet. En podcastinnspilling som trenger live
transkripsjon kan prioriteres over en bakgrunns-oppsummering.
### Fast utviklingspunkt
To team (eller to hatter) med klart grensesnitt:
- **Over maskinrommet:** primitiver, noder, edges, UI, brukeropplevelse
- **I maskinrommet:** ytelse, integrasjoner, kapasitet, kostnad
Du kan perfeksjonere det ene uten å røre det andre.
## Content-Addressable Storage og intelligent pruning
Maskinrommet forvalter også lagring. Ikke alt kan lagres for evig — men
ikke alt trenger det heller. Signalene for hva som er viktig finnes
allerede i grafen.
### CAS som lagringsprimitiv
All binærdata (lyd, bilde, video) lagres i et content-addressable store.
Fordeler:
- **Deduplisering gratis** — samme fil delt i tre kontekster = én kopi
- **Separasjon** — "innholdet eksisterer" er adskilt fra "innholdet er
tilgjengelig." Noden peker på en hash, CAS har filen (eller ikke).
- **Enkel opprydning** — slett hashen fra CAS, alle noder som pekte
dit mister binærdataen men beholder metadata og transkripsjon.
### Lagringsregler per modalitet
| Modalitet | Default levetid | Begrunnelse |
|-----------|----------------|-------------|
| Tekst | Evig | Billig, er essensen av innholdet |
| Transkripsjon | Evig | Tekstlig representasjon av lyd/video — tar vare på meningen |
| Lyd | 30 dager | Mellomkostnad, transkripsjon bevarer innholdet |
| Bilde | 30 dager | Mellomkostnad, beskrivelse/metadata bevarer kontekst |
| Video | 14 dager | Dyrest, transkripsjon + thumbnail bevarer det meste |
### Signaler som forlenger levetid
Default-TTL er bare utgangspunktet. Maskinrommet justerer basert på:
- **Edges.** En lydfil med edge til episoderegisteret = publisert
podcast, beholdes. En privat voice-memo uten edges = 30-dagers TTL.
- **Aksesslog.** Hvis noen har spilt av lydfilen i løpet av TTL-perioden,
forlenges den. Ingen aksess = ingen verdi i å beholde binærdataen.
- **Transkripsjonsstatus.** Lyd som er transkribert har "overlevert sin
essens" til tekst. Lyd som *ikke* er transkribert (f.eks. musikk,
lydeffekter) kan trenge lengre TTL.
- **Edge-type.** Edge til publisert innhold = behold. Edge til arkivert
møte = transkripsjon holder. Edge til ingenting = teksten lever videre,
binærdataen kan dø.
### Eksempler
```
Privat voice-memo, aldri delt:
→ Lyd transkriberes → tekst lagres evig
→ Lydfil: 30 dager, ingen aksess, ingen edges → slettes
→ Noden lever videre med teksten
Podcastepisode:
→ Lyd har edge til episoderegister + publiserings-edge
→ Aksesseres regelmessig via podcastarkivet
→ Lydfil: beholdes så lenge edges og aksess tilsier det
Rutinemøte for et år siden:
→ Video (6 kanaler): ingen har sett den på 6 måneder → slettes
→ Lyd: ingen har spilt av → slettes
→ Transkripsjon: tekst, lagres evig. Søkbar, refererbar.
→ Noden lever med full kontekst minus binærdata
Viktig styremøte:
→ Video aksesseres av styremedlemmer → forlenges
→ Workspace-innstilling: "behold video i 1 år" → overrider default
```
### Generert innhold er en cache
TTS, thumbnails, AI-oppsummeringer, waveforms — alt som kan regenereres
fra kildedata er i praksis en cache. Det lagres i CAS med samme TTL-
mekanisme som alt annet:
- Peter ber om lyd-versjon av en tekstmelding → TTS genereres, lagres
- Ingen spiller den av på 30 dager → filen slettes fra CAS
- Peter (eller noen andre) ber om lyd igjen → regenereres on-demand
- Teksten er der alltid. Binærdataen er flyktig.
Maskinrommet trenger ikke skille mellom "original lyd" (voice memo) og
"generert lyd" (TTS) i pruning-logikken. Begge er binærdata i CAS med
en TTL som forlenges ved aksess. Forskjellen er bare at generert
innhold alltid kan gjenskapes fra kilden — så det er tryggere å prune.
### Workspace-styrt aggressivitet
Hvert workspace kan justere sin pruning-profil:
- **Konservativt** — behold alt lenge (f.eks. arkiv-workspace)
- **Aggressivt** — tekst bevares, binærdata prunes raskt (f.eks.
daglig drift-workspace med mye rutineinnhold)
- **Tilpasset** — egne regler per modalitet og edge-type
### Brukerens erfaringsbaserte meny
Over tid bruker du noen edges oftere enn andre, noen noder oftere enn
andre. Maskinrommet observerer dette og tilbyr en erfaringsbasert meny:
dine mest brukte koblinger, dine vanligste input-mønstre, dine
foretrukne modaliteter. Ikke som en rigid konfigurasjon — som en
adaptiv overflate du kan aktivere og deaktivere fortløpende.
Dette er ikke maskinlæring eller kompleks AI — det er frekvenstelling
på edges og aksesslog. Enkelt å implementere, intuitivt for brukeren.
## Pragmatisk vei dit
Ikke bygg dette fra scratch. Formaliser det som allerede finnes:
1. **Worker + jobbkø er allerede kjernen.** De trenger et konsistent
API, ikke en omskriving.
2. **AI Gateway (LiteLLM) absorberes** — i stedet for en separat proxy,
blir LLM-kall en operasjon i maskinrommet som alt annet.
3. **Whisper, TTS, mediaprosessering** — allerede planlagt som
worker-jobber. Gi dem samme grensesnitt.
4. **LiveKit** — den mest spesielle tjenesten (sanntidsstrømmer). Kan
starte som en separat integrasjon og formaliseres inn over tid.
Rekkefølge: definer traits → migrer eksisterende worker-jobber inn →
legg til nye tjenester etter hvert. Fast punkt fra dag én, full
dekning over tid.
## Spenninger og åpne spørsmål
- **Synkron vs asynkron.** "Fang" og "lever" kan være instant, men
"prosesser" kan ta sekunder (TTS) eller minutter (full episode-
transkripsjon). Grensesnittet må håndtere begge naturlig.
- **Strømmer.** Live lyd/video er fundamentalt annerledes enn
request/response. Men edge-modellen løser mye: maskinrommet ser en
stream-edge og vet at det betyr LiveKit. Utfordringen er *reaktivitet*
— maskinrommet må observere edge-endringer i sanntid og koble inn/ut
ressurser dynamisk.
- **Granularitet.** Hvor mye skal maskinrommet vite om domenet? "Fang
lyd" er generisk, men "transkriber og splitt i segmenter med
taler-identifikasjon" er domenespesifikt. Hvor går grensen?
- **Overhead.** Et ekstra lag betyr et ekstra kall. For tunge
operasjoner (Whisper, LLM) er det neglisjerbart. For lette
operasjoner (slå opp metadata) kan det være unødvendig indirection.
## Plassering i lagmodellen
Maskinrommet (Rust) er det eneste orkestringslaget. Alle tjenester —
inkludert PG, SpacetimeDB, CAS, Whisper, LiteLLM, LiveKit — er
likeverdige tjenester *under* maskinrommet. SpacetimeDB er ikke et lag
mellom Rust og GUI, det er en tjeneste maskinrommet skriver til.
Ett unntak: SpacetimeDB har en direkte WebSocket-kobling til frontend
for sanntids lese-strøm. Dette er en bevisst optimering — STDB sitt
klient-SDK gir ~10μs-oppdateringer med automatisk synk og lokal cache.
Å proxy dette gjennom Rust ville vært å bygge en dårligere versjon av
noe STDB gjør optimalt.
Se [datalaget](datalaget.md) for full lagmodell med diagram.
## Forhold til andre retninger
Maskinrommet er infrastrukturen *under* de tre primitivene i
[universell input og mottak](universell_input.md):
- Input-primitiven kaller `fang()` + `prosesser()`
- Mottak-primitiven kaller `lever()`
- Kommunikasjonsnoden bruker alle tre (fang input fra deltakere,
prosesser sanntid, lever til mottakere)
Det er også det som gjør to-lags-modellen fra [rom, ikke forum](rom_ikke_forum.md)
praktisk: maskinrommet ruter til riktig lag (sanntid vs tradisjonelt)
uten at primitivene trenger å vite forskjellen.

View file

@ -0,0 +1,211 @@
# Rom, ikke forum
> Hva om Sidelinja ikke er en webapp med sanntidsfunksjoner, men en oppslukende
> sanntidsopplevelse som tilfeldigvis leverer tradisjonell funksjonalitet?
## Observasjoner
Sidelinja har vokst organisk. Vi har bygget chat, kanban, kalender, notater,
kunnskapsgraf — hver som sin feature, hver med sin spec. SpacetimeDB ble lagt til
for sanntid, men arkitekturen er fortsatt "PostgreSQL-app med sanntidskrydder."
Resultatet:
- **Forum-følelsen.** Ting er organisert i tråder, kort, lister. Brukeren
navigerer mellom sider. Det føles som et tradisjonelt verktøy med litt polish.
- **Databasespenning.** PG og SpacetimeDB har et komplisert eierforhold.
SpacetimeDB-loven løser grensesnittet, men ikke det underliggende spørsmålet:
hva *er* primæropplevelsen?
- **Feature-fragmentering.** Chat, kanban, whiteboard, notater — hver lever i sin
boks. "Universell overføring" og "meldingsboks" prøver å lime dem sammen, men
utgangspunktet er fortsatt separate primitiver.
## Tesen
Snu gravitasjonen:
**I stedet for:** Tradisjonell webapp → bolt på sanntid der det trengs
**Tenk:** Sanntidsrom er default → persistens er en egenskap ved ting som skjer
Forskjellen er subtil men fundamental:
- Et "dokument som flere kan redigere" vs "et rom der folk er sammen og ting
de gjør blir husket"
- "Gå til kanban-brettet" vs "åpne kanban-laget i rommet du allerede er i"
- "Send en melding i chat" vs "si noe i rommet"
## Hva ville vært annerledes?
### SpacetimeDB som verden, PG som arkiv
SpacetimeDB er ikke en "sanntidskache foran PG" — det er verdenen brukerne
lever i. PG er arkivet som husker hva som har skjedd.
Rollene blir klare og adskilte:
- **SpacetimeDB** = sanntidslaget. Aktivt samarbeid, live interaksjon,
ting som skjer *nå*.
- **PostgreSQL** = arkivet. Alt som noensinne har skjedd. Søk, historikk,
statistikk, revisjon.
Men viktig: sanntidslaget er *bare* et sanntidslag. Ikke alt trenger
sanntid. En kunnskapsgraf-utforsker, et søk i gamle episoder, en
statistikkside, en offentlig publisert artikkel — disse snakker rett med
PG-arkivet som tradisjonelle nettsider. De trenger ikke gå gjennom
SpacetimeDB. Begge lagene leser og skriver til det samme arkivet.
Dermed har vi to parallelle overflater:
- **Sanntidsopplevelsen** (via SpacetimeDB) — for alt som er levende,
aktivt, samarbeidende. Chat, whiteboard, live redigering.
- **Tradisjonelt lag** (rett mot PG) — for alt som er retrospektivt,
utforskende, statisk. Arkiv, søk, publisering, statistikk.
Dataflyt mellom dem: ting som oppstår i sanntidslaget synkes til PG.
Ting i PG kan løftes inn i sanntidslaget når de blir aktive igjen.
Men det er ingen eierskapskonflikt — de to lagene har fundamentalt
forskjellige roller. Det er ikke to konkurrerende sannheter, det er
*nåtid* og *arkiv*, med to overflater som passer til hver sin rolle.
### Rommet som primitiv, ikke siden
I dag navigerer brukeren mellom `/chat`, `/kanban`, `/kalender`. I "rom"-modellen
er brukeren alltid *et sted*, og funksjonalitet er lag som kan slås av og på:
chat-laget, oppgave-laget, tidslinje-laget. Alt eksisterer i samme sanntidsrom.
### Tilstedeværelse som førsteklasses konsept
Hvem er her nå? Hva ser de på? Hva jobber de med? I en tradisjonell webapp er
dette en "feature" (online-indikator). I rom-modellen er det fundamentet alt
annet bygger på.
### Interaksjon før organisering
I dag: lag et kanban-kort → fyll ut feltene → flytt det. I rom-modellen: si noe,
tegn noe, del noe → det som oppstår kan *bli* et kort, en oppgave, en notis —
organisering skjer etterpå, ikke som forutsetning.
## Den administrative opplevelsen
Tesen ovenfor kan føles abstrakt. Mer konkret: Sidelinja bør være en
**administrativ opplevelse** — ikke et spill med avatarer, men et arbeidsmiljø
der produktive ting er lette å oppnå og strukturen følger deg, ikke omvendt.
### Formløs input, struktur etterpå
I dag velger du visning først: "nå er jeg i chat", "nå er jeg i kanban." Men
meldingsboksen er allerede designet for at input ikke trenger å vite hva den
*er* på forhånd. En tanke bør kunne starte som en løs setning og bli et
kanban-kort, en faktoid, en kalenderoppføring — uten at brukeren måtte
bestemme det på forhånd. Visninger er flyktige linser. Innhold er permanent.
### Trylle frem, legge fra seg
Trenger du et whiteboard? Det dukker opp. En videosamtale? Den starter.
En dagbok? Den er der. Og når fokuset tar en annen retning legger du det
fra deg uten at det blir borte — kunnskapsgrafen holder styr på det. Du
trenger ikke "lukke" noe eller "lagre" noe. Ting eksisterer i verden
og kan gjenfinnes.
### Siloer forsvinner
"Universell overføring" som eksplisitt feature blir overflødig fordi det
ikke finnes separate steder å overføre *mellom*. Du endrer ikke *hvor*
noe er — du endrer *hvordan* du ser på det som allerede er der. Chat,
kanban, kalender er ikke apper — de er visninger av samme tilstandsrom.
### Privat og delt som lag, ikke separate systemer
"Personlig workspace" trenger ikke være en egen ting. Privat/delt er bare en
synlighetsbryter på alt du gjør. Du chatter med en kollega og samtidig skriver
du dagbok — begge er meldingsbokser, den ene er delt, den andre er privat.
De eksisterer side om side i samme rom.
Dette åpner for naturlig flyt mellom kontekster: du sitter på bussen, snakker
inn en tanke via voice, den transkriberes automatisk og lander som en privat
meldingsboks — dagboknotis, oppgavepåminnelse, idé til neste episode. Du
trenger ikke åpne "dagbok-appen" eller "notat-appen." Du bare snakker, og
systemet tar vare på det riktig sted basert på kontekst og synlighet.
Input-metode (tekst, voice, tegning) og synlighet (privat, delt, publisert)
er ortogonale egenskaper. Ingen av dem bør diktere *hva* innholdet blir.
### SpacetimeDB som naturlig motor
SpacetimeDB tenker "dette eksisterer i verden nå" — ikke "lagre dette i
riktig tabell." Å trylle frem et whiteboard er naturlig i en verden-modell:
det er bare et nytt objekt med en tilstand. I PG-modellen må du opprette
rader, definere relasjoner, sette opp persistens. SpacetimeDB låner seg
til flytende, formløs interaksjon på en måte PG ikke gjør.
PG briljerer i rollen som arkiv: relasjonelle spørringer over historikk,
fulltekstsøk, pgvector for semantisk søk, aggregeringer og statistikk.
Når du spør "hva snakket vi om i mars?" er det PG som svarer. Når du
spør "hva skjer nå?" er det SpacetimeDB.
## Spenninger og åpne spørsmål
- **Ytelse.** En alltid-på sanntidsopplevelse krever mer av både klient og
server enn tradisjonelle sideinnlastinger. Er SpacetimeDB klar for dette?
- **Kompleksitet.** "Alt er et rom" høres elegant ut, men kan bli kaotisk.
Hvordan unngår vi at det blir uoversiktlig?
- **Discovery vs fokus.** "Alt kan bli hva som helst" er kraftig, men kan
bli overveldende. Et whiteboard du tryller frem må være like lett å finne
igjen om tre uker. Kunnskapsgrafen er kanskje svaret — den er allerede
designet for å koble ting uavhengig av type.
- **~~Gradvis overgang.~~** *(Løst — se "Innebygd utviklingsstrategi" nedenfor.)*
- **Solo-bruk.** Mye av verdien i "rom" kommer fra å være der sammen. Hvordan
føles det for én person som jobber alene?
- **Er det vi allerede har?** Meldingsboks-konseptet, universell overføring og
SpacetimeDB-loven peker allerede i denne retningen. Kanskje dette ikke er en
ny retning, men en artikulering av det vi ubevisst har bygget mot?
- **Inspirasjon.** Spillverdener (MMO-lobbyer, shared spaces), Figma (alle i
samme canvas), tldraw, Gather.town. Hva kan vi lære fra disse?
## Innebygd utviklingsstrategi
To-lags-modellen gir en viktig implikasjon for utviklingen: **innebygd fallback
og to veier inn til alt.**
Ny funksjonalitet kan alltid starte i det tradisjonelle laget — rett mot PG,
vanlig request/response, kjent terreng. Den fungerer med en gang. Når det
senere gir verdi (samarbeid, sanntid, live interaksjon) kan den løftes inn
i sanntidslaget. Men den trenger ikke det for å være nyttig.
Dette betyr:
- **Ingenting vi har bygget er bortkastet.** Eksisterende PG-baserte features
er ikke "gammel arkitektur" — de er det tradisjonelle laget, og det er et
fullverdig lag.
- **Ingen stor omskriving.** Retningen er ikke en migrasjon med en frist. Den
er en utviklingsstrategi: bygg tradisjonelt, løft til sanntid ved behov.
- **Risikoen er lav.** Hvis SpacetimeDB skuffer, har vi fortsatt et komplett
tradisjonelt system. Hvis det leverer, får ting gradvis en rikere opplevelse.
- **Primitivene er nøkkelen.** Så lenge meldingsboksen og kunnskapsgrafen er
fleksible nok, kan begge lag bruke dem. Arkitekturen holder uavhengig av
hvilket lag en feature lever i.
## Kritisk vurdering
### SpacetimeDB er en ung teknologi
Å lene seg tungt på SpacetimeDB er et veddemål. Hvis prosjektet stagnerer,
endrer API, eller har skaleringstak vi ikke ser ennå — er vi eksponert.
*Mildnet* av at det tradisjonelle laget mot PG alltid finnes som fallback:
SpacetimeDB er ikke alt-eller-ingenting, men et lag for det som trenger
sanntid. Resten lever trygt mot PG uansett.
### "Formløs input" er vanskelig i praksis
Det høres elegant ut, men noen må bestemme hva noe *blir*. AI? Brukeren
etterpå? Automatiske regler? Notion, Roam Research og mange andre startet
med lignende ambisjoner og endte opp med å gi brukeren eksplisitte
strukturverktøy — fordi ambiguitet i praksis skaper friksjon, ikke flyt.
Risikoen er at vi bygger noe som føles magisk i demoen og forvirrende i
hverdagen.
### Grensen mellom "nåtid" og "arkiv" er uklar
En samtale fra i går som fortsatt er relevant — er den "nå" eller "arkiv"?
Et kanban-kort som har stått stille i to uker? Vi trenger regler for når ting
flyttes mellom lagene, og de reglene kan bli like komplekse som
SpacetimeDB-loven de erstatter. "Tid og arkiv" er renere *i prinsippet*,
men i praksis er "aktiv" et spektrum, ikke en binær tilstand.
### Solo-bruk er underadressert
Vegard er primærbruker. "Rom"-konseptet henter mye av sin kraft fra
tilstedeværelse og samarbeid. For én person som produserer podcast er det
en reell risiko at sanntidslaget er overhead sammenlignet med et godt
organisert tradisjonelt verktøy. *Mildnet* av to-lags-modellen: solo-bruk
kan lene seg tyngre på det tradisjonelle PG-laget, og sanntidslaget
aktiveres når det faktisk gir verdi (live innspilling, samarbeid).
### Omfanget
*Betydelig mildnet* av utviklingsstrategien ovenfor. Retningen krever ikke
en omskriving — den er kompatibel med inkrementell utvikling. Den
gjenværende risikoen er mer subtil: at vi bruker mental energi på å
vurdere "bør dette være sanntid?" for hver feature i stedet for å bare
bygge. Pragmatisk default bør være: bygg tradisjonelt, løft senere.

View file

@ -0,0 +1,72 @@
# Status quo — Hva Sidelinja er i dag
> En redaksjonell webapp med ambisiøse primitiver og tradisjonell overflate.
## Hva fungerer
### Meldingsboksen som universell primitiv
Den viktigste arkitekturbeslutningen. Én datamodell — meldingsboksen — er
underlag for chat, kanban-kort, kalenderoppføringer, notater og faktoider.
I stedet for fem separate domenemodeller har vi én fleksibel primitiv med
view-konfigurasjoner oppå. Dette er genuint uvanlig og gir oss muligheter
de fleste redaksjonelle verktøy ikke har.
### Kunnskapsgrafen
Nodes og edges i PostgreSQL gir en rik struktur for å koble alt med alt —
personer, temaer, episoder, fakta. Dette er ryggraden i det redaksjonelle
arbeidet og skiller Sidelinja fra enklere verktøy.
### Self-hosted med full kontroll
Hetzner VPS, Caddy, Authentik, Forgejo. Ingen avhengighet til skytjenester
vi ikke kontrollerer. For et journalistisk verktøy er dette ikke bare
en preferanse — det er et prinsipp.
### AI som infrastruktur, ikke feature
LiteLLM som gateway, BYOK-modell, Whisper for transkripsjon. AI er ikke
en knapp i UI-et — det er en del av maskineriet. Jobbkø-arkitekturen gjør
at tunge operasjoner aldri blokkerer brukeropplevelsen.
## Hva som er tradisjonelt
### Navigasjon
Brukeren beveger seg mellom `/chat`, `/kanban`, `/kalender` som separate
sider. Til tross for at datamodellen er universell, føles opplevelsen
fragmentert — som et sett med separate verktøy som deler database.
### Interaksjonsmodell
CRUD-mønsteret dominerer: opprett, rediger, slett, flytt. Interaksjonen
er form-basert og eksplisitt. Brukeren "administrerer innhold" mer enn
de "jobber sammen i et miljø."
### Sanntid som tillegg
SpacetimeDB er lagt til for å gi sanntidsoppdatering, men arkitekturen
er PostgreSQL-først. Sanntid er noe som *skjer med* tradisjonelle
operasjoner, ikke noe som er *grunnlaget* for opplevelsen.
## Spenninger
### To sannhetskilder
PG og SpacetimeDB har et komplisert forhold. SpacetimeDB-loven definerer
klare regler for hvem som eier hva, men selve eksistensen av loven vitner
om en arkitektonisk spenning: vi har to systemer som begge vil være
primærkilde, og vi bruker konvensjoner for å holde dem fra å kollidere.
### Ambisiøs bunn, forsiktig topp
Meldingsboksen og kunnskapsgrafen åpner for opplevelser vi ikke leverer
ennå. Datamodellen sier "alt henger sammen" — men UI-et sier "her er
chatten, her er kanban-brettet, her er kalenderen." Grunnmuren er mer
spennende enn det brukeren ser.
### Produksjonsverktøy vs opplevelse
Sidelinja er designet for podcast-produksjon, men pendler mellom å være
et effektivt arbeidsverktøy og noe mer oppslukende. Studioet og
møterommet peker mot sanntidsopplevelser. Redaksjonen og kanban peker
mot tradisjonelt prosjektstyringsverktøy. Begge er gyldige, men de
trekker i ulike retninger.
## Oppsummert
Sidelinja har en sterkere grunnmur enn overflaten viser. Meldingsboksen,
kunnskapsgrafen og AI-infrastrukturen er genuint interessante primitiver.
Spørsmålet er ikke om vi har bygget feil — men om overflaten utnytter
det fundamentet faktisk tillater.

View file

@ -0,0 +1,299 @@
# Universell input og mottak
> Én multimodal input-primitiv. Én personlig mottaksflate. Alt som fanges
> er samme type objekt. Hva det "er" bestemmes av edges, ikke av tabellen
> det ligger i. Hvordan det *presenteres* bestemmes av mottakeren.
## Observasjoner
I dag har vi meldingsboksen som "universell primitiv" — men den er egentlig en
*lagringsprimitiv*. Den samler chat, kanban-kort, kalenderoppføringer og notater
i én tabell, men *input* er fortsatt forskjellig per kontekst: chat har ett
tekstfelt, kanban har et skjema, kalender har en datovelger. Brukeren velger
kontekst først, deretter gir de input.
Og vi har separate pipelines for ulike modaliteter: tekst går én vei, lyd
(voice/transkripsjon) en annen, bilder en tredje. Hver med sin egen flyt.
## Tesen
**Én input-primitiv, to versjoner:**
- **Sanntidsversjon** — live i SpacetimeDB-laget. Brukes i rom, samarbeid,
samtaler. Streamer input og viser resultater i sanntid.
- **Flat versjon** — tradisjonelt mot PG. Brukes på bussen, alene, offline-aktig.
Fanger input og lagrer asynkront.
Begge aksepterer alt:
- **Tekst** — skriving, Markdown, kodeblokker
- **Lyd** — voice memo, diktering → automatisk transkribert
- **Bilde** — foto, skjermbilde, tegning
- **AI-støtte** — spør AI, få forslag, la den transformere input
- **Nettoppslag** — lim inn URL, den berikes automatisk
- **Kommunikasjon** — samme primitiv for alene (dagbok), en-til-en (melding),
gruppe (kanal)
Forskjellen mellom "jeg skriver dagbok", "jeg sender en melding" og "jeg lager
et kanban-kort" er ikke *hva brukeren gjør* — det er hvilke edges som knyttes
til resultatet.
## Én tabell, edges definerer alt
All output fra input-primitiven lander som noder i kunnskapsgrafen. Én tabell.
Ingen `messages`-tabell, ingen `cards`-tabell, ingen `notes`-tabell.
Hva en node "er" bestemmes utelukkende av edges:
- Node + edge til kanal = chatmelding
- Node + edge til board + status-edge = kanban-kort
- Node + edge til dato = kalenderoppføring
- Node + edge til kun bruker (privat) = dagboknotis
- Node + edge til topic = faktoid i kunnskapsgrafen
- Node uten edges = løs tanke, ennå uorganisert
**Retyping er trivielt.** Å gjøre en chatmelding om til et kanban-kort er å
legge til en edge til et board og en status-edge. Fjerne fra chat er å fjerne
kanal-edgen. Ingen datamigrering, ingen transformasjon av innhold. Bare edges.
**Multitype er naturlig.** En node kan være *både* et kanban-kort *og* en
kalenderoppføring *og* en faktoid. Det er ikke en edge case — det er
arkitekturen.
## Implikasjoner
### Meldingsboksen erstattes av noe dypere
Meldingsboksen var riktig intuisjon — men den er en lagringsprimitiv som
prøver å forene ulike domenemodeller. Universell input + kunnskapsgrafen
gjør det renere: det finnes bare noder og edges. "Meldingsboks" blir et
view-konsept (hvordan noder vises i en kontekst), ikke et lagrings-konsept.
### Input-metode og innholdstype er ortogonale
Du kan snakke inn et kanban-kort. Du kan tegne en kalenderoppføring. Du kan
skrive en voice memo (tekst som transkriberes til lyd for en annen bruker).
Input-primitiven bryr seg ikke om hva det *blir* — den fanger det som
kommer inn.
### Samme input, ulik routing
Lyd inn i input-primitiven kan routes helt forskjellig basert på edges:
- Edge til et møterom → streames live til andre deltakere (sanntidslaget)
- Edge til kun deg selv → transkriberes og lagres som personlig notat
- Edge til en podcast-kanal → goes into produksjonspipeline
- Edge til en person → sendes som lydmelding
Brukeren gjør det samme — snakker inn i input-feltet. *Systemet* router
basert på kontekst og edges. Det er ingen "møte-app" eller "notat-app"
eller "meldings-app" — det er én input med ulike destinasjoner.
### Mottaker bestemmer format
All lyd transkriberes. All tekst kan leses opp (TTS). Noden har alltid
begge representasjoner. Mottaker setter sin preferanse:
- Trond snakker inn en tanke → node med lyd + transkripsjon
- Peter har tekst-preferanse → ser transkripsjonen
- Vegard har lyd-preferanse → hører originallyd
- Anna skriver tekst → node med tekst + TTS-versjon
- Trond har lyd-preferanse → hører TTS-opplesning av Annas tekst
Senderen trenger ikke vite eller bry seg. Innholdet er det samme —
presentasjonen er en mottaker-side preferanse. Modalitet er ikke
en egenskap ved meldingen, men ved *lesningen* av den.
### Én overflate å perfeksjonere
Brukerens mentale modell kollapser til én ting: input-feltet. All
UX-investering konsentreres ett sted i stedet for å smøres tynt utover
ti ulike grensesnitt. Én perfekt input-opplevelse — responsiv, multimodal,
med god AI-støtte — i stedet for ti middelmådige spesialgrensesnitt.
Dette er en radikal forenkling av utviklingsoverflaten. I stedet for å
bygge og vedlikeholde chat-input, kanban-skjema, kalender-dialog,
notat-editor, voice-recorder, dagbok-felt — bygger vi *ett* grensesnitt
og investerer alt i å gjøre det feilfritt. Alt etterpå er edges.
### Visninger er spørringer mot grafen
Chat-visningen = "vis noder med edge til denne kanalen, sortert på tid."
Kanban-visningen = "vis noder med edge til dette boardet, gruppert på status."
Kalender-visningen = "vis noder med dato-edge, plassert på tidslinje."
Dagbok-visningen = "vis private noder for denne brukeren, sortert på tid."
Alle visninger leser fra samme graf. Ingen har "sin egen" data.
### To versjoner passer to-lags-modellen
Sanntidsversjonen lever i SpacetimeDB-laget: input streames, resultater er
live, andre ser hva du gjør. Flat versjonen lever i det tradisjonelle laget:
input sendes, lagres i PG, ferdig. Begge produserer identiske noder i grafen.
### Synlighet er bare en edge
Privat = edge kun til deg. Delt = edge til en gruppe/kanal. Publisert = edge
til en offentlig kontekst. Å "dele" noe er å legge til en edge. Å "gjøre
privat" er å fjerne den. Innholdet endres aldri.
## Universelt mottak — den andre primitiven
Input-primitiven er halvparten. Den andre halvdelen er *mottak*: hvordan
du konsumerer det andre produserer. Der input er "én overflate som fanger
alt", er mottak "én overflate som presenterer alt tilpasset *deg*."
### Dimensjoner ved mottak
**Format.** Lyd, tekst, visuelt — mottaker bestemmer (allerede beskrevet
over). Men det gjelder alt, ikke bare meldinger: en AI-oppsummering kan
leses eller høres. Et whiteboard-snapshot kan vises som bilde eller som
tekstlig beskrivelse.
**Filtrering.** Hva ser du? Alt fra alle er støy. Mottaksflaten filtrerer
basert på dine edges: hvilke kanaler du følger, hvilke personer du
samarbeider med, hvilke topics du er interessert i. Du kuraterer ikke
manuelt — du justerer edges, og mottaksflaten oppdateres.
**Prioritering.** Hva er viktig *nå*? En AI-assistert redaksjonell flate
som løfter frem det som trenger oppmerksomhet: ubesvarte meldinger,
oppgaver med frist, noder som er endret siden sist, tråder med aktivitet.
Ikke en notifikasjonsliste — en *vektet visning* av det som er relevant.
**Tempo.** Sanntid eller asynkront. I sanntidslaget: ting streamer inn
mens de skjer — en kollega snakker, du hører/leser live. I det
tradisjonelle laget: du får en digest, en oppsummering, et overblikk
over hva som har skjedd siden sist. Samme noder, ulikt tempo.
**Kilde.** Direkte fra en person, eller via en node. Et møte som
genererer innsikter. En AI-jobb som er ferdig. En tråd som har blitt
aktiv igjen. En podcast-episode som er klar for review. Kilden trenger
ikke være et menneske — det kan være en prosess, en hendelse, en
tilstandsendring i grafen.
### Mottaksflaten som speilbilde av input
| Input | Mottak |
|-------|--------|
| Én overflate for all input | Én overflate for alt mottak |
| Sender bestemmer ikke format | Mottaker bestemmer format |
| Modalitet er ortogonal | Presentasjon er ortogonal |
| Kontekst gir edges | Preferanser gir filtrering |
| Sanntid + flat versjon | Sanntid (stream) + asynkron (digest) |
### Mange-til-én og mange-via-mange
Mottaksflaten håndterer naturlig:
- **Én-til-én** — Trond sender deg en melding
- **Mange-til-én** — fem personer i en kanal, du ser alt
- **Via node** — et møte genererer et referat, du mottar det
- **Via kjede** — en chatmelding → blir oppgave → oppgaven fullføres →
du får oppdatering. Hele kjeden er edges, og du ser resultatet i din
mottaksflate uten å ha fulgt hvert steg.
### Mottaksflaten er også en visning av grafen
Akkurat som chat-visningen er "noder med kanal-edge sortert på tid", er
mottaksflaten "noder med edge til *meg*, vektet på relevans og tid."
Det er ikke en egen mekanisme — det er enda en spørring mot samme graf,
bare med *deg* som sentrum.
## Kommunikasjonsnoden — den tredje primitiven
Input fanger. Mottak presenterer. Men det mangler noe: *stedet* der folk
møtes. En kommunikasjonsnode er en node i grafen som samler deltakere,
definerer tilgangsregler, og fungerer som kontekst for input og mottak.
### Én node, mange former
En kommunikasjonsnode er konseptuelt identisk uansett skala:
| Variant | Deltakere | Input-tilgang | Mottak-tilgang |
|---------|-----------|---------------|----------------|
| Én-til-én samtale | 2 | Begge | Begge |
| Gruppechat | N | Alle medlemmer | Alle medlemmer |
| Redaksjonsmøte | N | Alle medlemmer | Alle medlemmer |
| Allmøte | 1 + N | Lederen snakker | Alle lytter, noen kan rekke opp hånden |
| Podcastinnspilling | 2-4 + N | Vertene snakker | Alle lytter, markører for produsent |
| Livesending | 1-4 + ∞ | Vertene | Streamet til nettside/app, lyd eller video |
| Asynkron gjest | 1 + 1 | Gjest gir input innen frist | Redaksjonen mottar |
Forskjellen er *ikke* ulike systemer — det er ulike edge-konfigurasjoner
på samme nodetype:
- **Eier-edge** — hvem kontrollerer noden (kan invitere, endre regler, avslutte)
- **Input-edge** — hvem kan gi input (snakke, skrive, tegne, dele skjerm)
- **Mottak-edge** — hvem kan motta (lytte, lese, se stream)
- **Rolle-edge** — spesialroller (moderator, produsent, gjest)
### Kommunikasjonsnoden er en kontekst for de andre primitivene
Når du gir input *i* en kommunikasjonsnode, arver inputen kontekst-edges
automatisk. Sier du noe i et møte → noden du skaper får edge til møtet.
Du trenger ikke tenke på det — konteksten følger med.
Mottak i en kommunikasjonsnode er det samme som universelt mottak, bare
scoped til den noden: du ser/hører det andre deltakere gir som input,
presentert etter dine preferanser.
### Livssyklus
En kommunikasjonsnode kan være:
- **Live** — aktiv i sanntidslaget. Deltakere er til stede, input streames.
- **Asynkron** — aktiv i det tradisjonelle laget. Deltakere gir input i
eget tempo (chat, asynkron gjest).
- **Avsluttet** — arkivert i PG. Alt som ble sagt/delt er noder med edges
til kommunikasjonsnoden. Kan søkes, gjenfinnes, refereres.
- **Gjenåpnet** — løftet tilbake til sanntidslaget. "Vi tar opp tråden
fra forrige møte" er bokstavelig talt å reaktivere en node.
### Skalering er en edge-endring, ikke en migrasjonsoperasjon
En samtale mellom to blir et møte ved å legge til flere deltaker-edges.
Et møte blir en livesending ved å legge til offentlige mottak-edges.
En livesending blir en podcast ved å legge til publiserings-edges på
arkivert innhold. Ingen migrering, ingen konvertering — bare edges.
## Tekniske forutsetninger
### STT (tale → tekst): løst
Faster-whisper kjører lokalt, god norsk kvalitet. Allerede i stacken.
### TTS (tekst → tale): løsbart
Norsk TTS lokalt er ikke godt nok ennå (Piper er "usable" men dårlig rytme,
XTTS-v2 støtter ikke norsk). Kommersielt er det løst:
- **ElevenLabs** — beste kvalitet, eksplisitt norsk med regionale aksenter
- **Azure Neural TTS** — god kvalitet, ~$15/M tegn
- **Google Cloud TTS** — god kvalitet, WaveNet/Neural2
Strategi: start med kommersiell API (ElevenLabs) bak AI Gateway, bytt til
lokal modell (Chatterbox Multilingual el.l.) når kvaliteten er god nok.
Brukeren merker ingenting — det er en backend-swap bak gatewayen. Samme
mønster som Whisper: tung jobb → jobbkø → worker → resultat som node-metadata.
Følg med på: **Chatterbox Multilingual** (Resemble AI) — annonsert norsk
støtte, 350M params, lovende for lokal kjøring.
## Spenninger og åpne spørsmål
- **Ytelse.** Én tabell med *alt* i — skalerer det? PG med riktige indekser
og partisjonering håndterer mye, men det er en reell designbeslutning.
- **Skjema.** Noder trenger noe felles skjema (innhold, created_at, author).
Men ulike modaliteter har ulike metadata (transkripsjon, bildestørrelse,
varighet). Hva er felles og hva er edge-metadata?
- **AI-klassifisering.** Når brukeren bare "sier noe" — hvem bestemmer hvilke
edges som knyttes? Manuelt? AI-foreslått? Kontekstbasert (sa det i en
kanal → kanal-edge)? Sannsynligvis en blanding, men det krever gjennomtenkt
UX.
- **Migrering.** Meldingsboksen har allerede innhold. Kan vi migrere til
node+edge-modellen gradvis, eller er det et brudd?
- **Kompleksitet for utviklere.** "Alt er noder og edges" er konseptuelt rent,
men å bygge en kanban-visning som spørrer en graf er mer komplekst enn å
lese fra en `cards`-tabell. Er abstraksjonen verdt kompleksiteten?
## Tre primitiver, én graf
| Primitiv | Hva den gjør | Brukerens opplevelse |
|----------|-------------|---------------------|
| **Input** | Fanger alt — tekst, lyd, bilde, AI | Én overflate å snakke/skrive/tegne i |
| **Mottak** | Presenterer alt tilpasset deg | Én personlig flate med det som er relevant |
| **Kommunikasjon** | Samler folk med tilgangsregler | Et sted å være — samtale, møte, sending |
Alt er noder og edges i samme graf. Input skaper noder. Mottak spør
grafen med deg som sentrum. Kommunikasjonsnoder gir kontekst og
tilgangsregler. Visninger (chat, kanban, kalender, dagbok, stream)
er bare spørringer med ulike filtre.
## Forhold til andre retninger
Denne retningen konkretiserer [rom, ikke forum](rom_ikke_forum.md):
- "Formløs input, struktur etterpå" → universell input + edges
- "To lag" → sanntidsversjon + flat versjon
- "Privat/delt som lag" → synlighet som edge
- "Siloer forsvinner" → alt er noder, visninger er spørringer
- "Rommet som primitiv" → kommunikasjonsnoden

20
ops/README.md Normal file
View file

@ -0,0 +1,20 @@
# Ops — Repeterbare vedlikeholdsjobber
Denne mappen inneholder veldefinerte, repeterbare jobber for å holde prosjektet
ryddig og gjennomsiktig. Hver jobb er en markdown-fil med sjekkliste som kan
kjøres av Claude eller manuelt.
## Jobber
| Jobb | Fil | Frekvens | Beskrivelse |
|------|-----|----------|-------------|
| Ryddejobb | [ryddejobb.md](ryddejobb.md) | Annenhver uke / ved behov | Full revisjon av prosjektet — docs, kode, drift, fremdrift |
| Doc-audit | [doc-audit.md](doc-audit.md) | Månedlig / etter store endringer | Sjekk at docs/ stemmer med faktisk kode |
| Drift-sjekk | [drift-sjekk.md](drift-sjekk.md) | Ved deploy / ved behov | Asynkron tilstand mellom prod, lokal og docs |
## Konvensjoner
- Hver jobb har seksjonene: **Hva**, **Når**, **Sjekkliste**, **Sist kjørt**
- «Sist kjørt»-seksjonen oppdateres hver gang jobben kjøres
- Funn skrives som korte bullet points med dato
- Fikser gjøres underveis eller logges som oppgaver i `tasks/`

46
ops/doc-audit.md Normal file
View file

@ -0,0 +1,46 @@
# Doc-audit — Dokumentasjon vs kode
## Hva
Målrettet gjennomgang av `docs/`-treet for å sikre at dokumentasjonen stemmer med
faktisk kode. Mer fokusert enn ryddejobben — kun docs.
## Når
- Månedlig som rutine
- Etter store refaktoreringer eller arkitekturendringer
- Når nye docs legges til
## Sjekkliste
### 1. Filreferanser
- [ ] Sjekk alle filstier nevnt i docs — eksisterer de?
- [ ] Sjekk alle komponent-/modul-navn nevnt i docs — eksisterer de i koden?
- [ ] Sjekk alle route-referanser — matcher de `web/src/routes/`?
### 2. CLAUDE.md doc-tre
- [ ] Er alle filer i `docs/` listet i CLAUDE.md doc-treet?
- [ ] Er det filer listet i CLAUDE.md som ikke eksisterer?
- [ ] Er beskrivelsene i doc-treet fortsatt dekkende?
### 3. Tekniske detaljer
- [ ] Stemmer database-skjema beskrevet i docs med faktiske migrasjoner?
- [ ] Stemmer API-endepunkter beskrevet i docs med faktiske routes?
- [ ] Stemmer SpacetimeDB-tabeller/reducere i docs med modulen?
### 4. Setup-docs
- [ ] `docs/setup/lokal.md` — fungerer stegene fortsatt?
- [ ] `docs/setup/produksjon.md` — stemmer med faktisk server-oppsett?
- [ ] `docs/setup/migration_safety.md` — er sjekklisten oppdatert?
### 5. Proposals
- [ ] Er noen proposals implementert og bør flyttes til `concepts/` eller `features/`?
- [ ] Er noen proposals foreldet og bør slettes eller arkiveres?
- [ ] Er noen proposals egentlig retningsspørsmål og bør flyttes til `retninger/`?
### 6. Retninger
- [ ] Er tesene i `docs/retninger/` fortsatt relevante?
- [ ] Har noen retninger modnet nok til å påvirke andre docs (arkitektur, features, infra)?
- [ ] Er det nye arkitektoniske spenninger som fortjener en egen retning?
## Sist kjørt
_Ikke kjørt ennå._

45
ops/drift-sjekk.md Normal file
View file

@ -0,0 +1,45 @@
# Drift-sjekk — Prod vs lokal vs docs
## Hva
Verifiser at produksjonsserveren, lokalt utviklermiljø og dokumentasjon er i synk.
Fanger opp tilfeller der noe er deployet men ikke dokumentert, eller dokumentert
men ikke implementert.
## Når
- Før og etter deploy til produksjon
- Når noe oppfører seg annerledes i prod vs lokalt
- Ved mistanke om drift
## Sjekkliste
### 1. Git-status
- [ ] Er prod-server på siste commit? (`ssh sidelinja@157.180.81.26 'cd /srv/sidelinja/server && git log -1'`)
- [ ] Er det ucommittede endringer lokalt som burde vært pushet?
- [ ] Er det commits på Forgejo som ikke er deployet til prod?
### 2. Database-migrasjoner
- [ ] Er alle lokale migrasjoner pushet til repo?
- [ ] Er alle migrasjoner i repo kjørt i prod?
- [ ] Stemmer migrasjonsnumre mellom miljøer?
### 3. Docker-tjenester
- [ ] Kjører alle forventede containere i prod? (`docker compose ps`)
- [ ] Er det tjenester i `docker-compose.dev.yml` som mangler i prod (eller omvendt)?
- [ ] Er image-versjoner oppdatert?
### 4. Miljøvariabler
- [ ] Er det nye env-vars lagt til lokalt som mangler i prod `.env`?
- [ ] Er det env-vars i prod som er utdaterte?
- [ ] Er secrets rotert der de bør være?
### 5. SpacetimeDB-modul
- [ ] Er SpacetimeDB-modulen publisert med siste endringer?
- [ ] Stemmer modul-skjema mellom lokal og prod?
### 6. Caddy / reverse proxy
- [ ] Er Caddyfile i repo synk med prod?
- [ ] Er det nye subdomener eller routes som mangler?
## Sist kjørt
_Ikke kjørt ennå._

65
ops/ryddejobb.md Normal file
View file

@ -0,0 +1,65 @@
# Ryddejobb — Full prosjektrevisjon
## Hva
Systematisk gjennomgang av hele prosjektet for å oppdatere fremdrift, tette hull,
fjerne utdaterte referanser, og sikre at dokumentasjon stemmer med virkeligheten.
## Når
- Annenhver uke som rutine
- Etter store implementeringsjobber
- Når prosjektet føles uoversiktlig
## Sjekkliste
### 1. CLAUDE.md — stemmer instruksjonene?
- [ ] Er stack-beskrivelsen oppdatert?
- [ ] Er doc-treet komplett (alle filer i `docs/` er listet)?
- [ ] Er reglene fortsatt relevante?
- [ ] Finnes det nye konvensjoner som bør inn?
### 2. Docs vs virkelighet
- [ ] Gå gjennom `docs/concepts/` — stemmer beskrivelsene med hva som finnes i koden?
- [ ] Gå gjennom `docs/features/` — er det features beskrevet som ikke er påbegynt? Marker dem.
- [ ] Gå gjennom `docs/infra/` — stemmer infrastruktur-docs med `docker-compose.dev.yml` og prod?
- [ ] Gå gjennom `docs/setup/` — fungerer oppsettinstruksjonene fortsatt?
- [ ] Gå gjennom `docs/retninger/` — er tesene fortsatt relevante? Har noen modnet til beslutninger?
- [ ] Er det docs som refererer til filer, routes eller komponenter som ikke eksisterer?
### 3. Kode-hygiene
- [ ] Ubrukte SvelteKit-routes (mapper i `web/src/routes/` uten innhold eller med stub)
- [ ] Ubrukte komponenter (filer i `web/src/lib/components/` som ikke importeres)
- [ ] Ubrukte Rust-moduler i worker
- [ ] Ubrukte SpacetimeDB-reducere eller tabeller
- [ ] Gamle migrations som bør dokumenteres eller konsolideres
- [ ] `package.json` / `Cargo.toml` — ubrukte dependencies
### 4. Fremdriftsstatus
- [ ] Hva er faktisk implementert og fungerer?
- [ ] Hva er påbegynt men ufullstendig?
- [ ] Hva er kun planlagt (kun docs)?
- [ ] Oppdater en kort statusoversikt (kan legges i `ops/status.md` ved behov)
### 5. Asynkron tilstand — prod vs lokal vs docs
- [ ] Stemmer `docker-compose.dev.yml` med det som faktisk kjøres lokalt?
- [ ] Er prod-server oppdatert med siste push?
- [ ] Er det migrasjoner som er kjørt lokalt men ikke i prod (eller omvendt)?
- [ ] Er miljøvariabler (.env) synkronisert mellom miljøer?
### 6. CLAUDE.md minne
- [ ] Gå gjennom `~/.claude/projects/-home-vegard-server/memory/MEMORY.md`
- [ ] Fjern utdaterte minner
- [ ] Oppdater minner som har blitt unøyaktige
- [ ] Er det ny kunnskap fra nylige samtaler som bør lagres?
### 7. Erfaringslogg
- [ ] Er det gjort arbeid nylig som mangler erfaringsdokumentasjon i `docs/erfaringer/`?
- [ ] Er eksisterende erfaringsdokumenter fortsatt relevante og korrekte?
### 8. dev.sh og utviklermiljø
- [ ] Fungerer `./dev.sh` fra scratch?
- [ ] Er alle nødvendige tjenester dekket?
- [ ] Er det nye quirks eller workarounds som bør inn i scriptet?
## Sist kjørt
_Ikke kjørt ennå._

56
scripts/summary.sh Executable file
View file

@ -0,0 +1,56 @@
#!/usr/bin/env bash
# Samler all prosjektdokumentasjon til én fil for deling med AI-er etc.
# Bruk: ./collect-docs.sh → skriver scripts/server_context.md
# ./collect-docs.sh - → skriver til stdout (for piping)
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
OUT="$SCRIPT_DIR/summary.md"
files=(
# Overblikk — visjon og retning først
"$ROOT/docs/arkitektur.md"
"$ROOT/CLAUDE.md"
"$ROOT"/docs/retninger/*.md
# Hva vi bygger
"$ROOT"/docs/concepts/*.md
"$ROOT"/docs/features/*.md
# Hvordan det henger sammen
"$ROOT"/docs/infra/*.md
# Idéer og utforskning
"$ROOT"/docs/proposals/*.md
# Lærdommer og drift
"$ROOT"/docs/erfaringer/*.md
"$ROOT"/docs/setup/*.md
"$ROOT"/ops/*.md
# Databaseskjema
"$ROOT"/migrations/*.sql
)
collect() {
for f in "${files[@]}"; do
[[ -f "$f" ]] || continue
rel="${f#"$ROOT/"}"
echo "================================================================"
echo "FILE: $rel"
echo "================================================================"
echo ""
cat "$f"
echo ""
echo ""
done
}
if [[ "${1:-}" == "-" ]]; then
collect
else
collect > "$OUT"
echo "Wrote $OUT ($(wc -l < "$OUT") lines)"
fi