synops/docs/retninger/universell_input.md
vegard 0a467066ba Synops v2: arkitektur, retninger og dokumentasjon
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>
2026-03-17 06:43:08 +01:00

299 lines
14 KiB
Markdown

# 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