synops/docs/features/chat.md
vegard b5aa5bb243 Fjern SpacetimeDB komplett (oppgave 22.4)
SpacetimeDB er nå helt fjernet fra Synops. Sanntid håndteres av
PG LISTEN/NOTIFY + WebSocket i portvokteren (maskinrommet).

Kode fjernet:
- spacetimedb/ Rust-modul og spacetime.json
- maskinrommet/src/stdb.rs (HTTP-klient for STDB-reducers)
- frontend module_bindings/ (23 auto-genererte filer)
- spacetimedb npm-avhengighet fra package.json
- scripts/test-sanntid.sh (testet STDB-flyt)

Infrastruktur:
- Docker-container stoppet og fjernet fra docker-compose.yml
- Caddy: fjernet /spacetime/* reverse proxy
- maskinrommet-env.sh: fjernet STDB_IP og SPACETIMEDB_*-variabler
- .env.example: fjernet SpacetimeDB-seksjoner

Dokumentasjon oppdatert:
- CLAUDE.md: stack, lagmodell, kjerneprinsipper, driftsmodell
- docs/arkitektur.md: skrivestien, lesestien, datalag, teknologivalg
- docs/retninger/datalaget.md: migrasjonshistorikk, status "fjernet"
- 37 andre docs oppdatert (features, concepts, infra, ops, retninger)
- Alle kode-kommentarer med STDB-referanser oppdatert

Verifisert: maskinrommet bygger og starter OK, frontend bygger OK,
helsesjekk returnerer 200. Caddy reloadet.
2026-03-18 13:39:09 +00:00

9.2 KiB

Feature: Chat (Channels & Meldinger)

Filsti: docs/features/chat.md

1. Konsept

En universell, sanntids meldingskomponent. Chat er ikke bundet til én kontekst — den kan knyttes til enhver node i Kunnskapsgrafen via channels. Ulike konsepter bruker chat med ulik konfigurasjon, men all infrastruktur er delt. Sanntid via PG LISTEN/NOTIFY + WebSocket.

2. Channels-modellen

En channel er en meldingsstrøm knyttet til en vilkårlig node (parent) i grafen. Channelen er selv en node (node_type = 'channel'), noe som betyr at den deltar i grafen og arver tilgangsstyring via node_access-matrisen.

┌─────────────┐     parent_id      ┌─────────────┐
│   Channel   │ ──────────────────► │ Vilkårlig   │
│   (node)    │                     │ node        │
└──────┬──────┘                     └─────────────┘
       │
       │  channel_id
       ▼
┌─────────────┐
│  Meldinger  │
│  (nodes)    │
└─────────────┘

En node kan ha flere channels. Eksempler:

  • Et Tema har "Diskusjon" (default) + "Research-dump"
  • En Episode har "Redaksjonelt" (intern kommentartråd)
  • Et Møte har "Scratchpad" (flyktig, TTL)
  • En Aktør har "Notater" (valgfri)

2.1 Channel-konfigurasjon

Hver channel har en config (JSONB) som styrer hvilke capabilities den støtter:

{
  "threads": true,          // reply-to-tråder
  "mentions": true,         // #/@ parsing + automatiske graph_edges
  "attachments": true,      // filopplasting
  "research_clips": true,   // research_clip meldingstype (AI-prosessert)
  "ttl_days": null           // null = permanent, tall = auto-slett etter N dager
}

2.2 Standard channel-presets per konsept

Konsept Channel-navn Config
Redaksjonen (Tema) "Diskusjon" threads: true, mentions: true, attachments: true, research_clips: true, ttl_days: null
Redaksjonen (Tema) "Research" (valgfri) threads: false, mentions: true, attachments: true, research_clips: true, ttl_days: null
Møterommet "Scratchpad" threads: false, mentions: false, attachments: false, research_clips: false, ttl_days: 90
Studioet "Studio-chat" threads: false, mentions: false, attachments: false, research_clips: false, ttl_days: 30
Episode "Redaksjonelt" threads: true, mentions: true, attachments: true, research_clips: false, ttl_days: null

Presets er kun defaults — en admin for samlings-noden kan justere config per channel.

2.3 Automatisk opprettelse

Når en node opprettes som forventes å ha chat (Tema, Episode, Møte), oppretter systemet automatisk en default-channel. Dette skjer i SvelteKit server-side som en del av node-opprettelsestransaksjonen.

3. Meldinger

3.1 Datamodell (PostgreSQL)

Meldinger er noder i Kunnskapsgrafen (node_type = 'melding'):

messages (
    id           UUID PK  nodes(id),
    channel_id   UUID NOT NULL  channels(id),
    reply_to     UUID  messages(id),       -- tråder (hvis config.threads = true)
    author_id    TEXT NOT NULL  users,
    message_type message_type,              -- 'text', 'research_clip', 'factoid', 'system'
    body         TEXT NOT NULL,
    metadata     JSONB,                     -- ekstra data (research-klipp AI-resultat, etc.)
    edited_at    TIMESTAMPTZ,
    created_at   TIMESTAMPTZ
)

3.2 Sanntid

Nye meldinger skrives til PG og propageres via LISTEN/NOTIFY + WebSocket til alle tilkoblede klienter i sanntid.

4. Mentions & Autocomplete

Kun aktive når config.mentions = true.

  • Trigger-tegn: # (Temaer/Aktører fra Kunnskapsgrafen), @ (brukere/redaksjonsmedlemmer), / (kommandoer).
  • Filtrering: Svelte-klienten filtrerer den lokale cachen umiddelbart. Skriver man #Ha... vises en klikkbar liste ("Hans Petter Sjøli", "Høyre").
  • Grafkobling: Ved #-mention opprettes automatisk MENTIONS-edges i graph_edges mellom meldingen og den nevnte noden.
  • Mobil-optimalisert: Autocomplete-listen er tappbar og tilpasset mindre skjermer.

5. Tråder

Kun aktive når config.threads = true. Meldinger kan ha en reply_to-referanse. Frontend grupperer meldinger i tråder (rot + svar) med visuell skillelinje mellom hver tråd. Svar vises med innrykk og vertikal linje under rot-meldingen, uten ekstra skillelinje mellom rot og svar.

6. Vedlegg

Kun aktive når config.attachments = true. Meldinger kan ha vedlegg via message_attachmentsmedia_files. Whiteboard-eksport kan knyttes som vedlegg.

7. Versjonshistorikk

Alle meldinger støtter redigering med full historikk via message_revisions. Original tekst bevares alltid. AI-behandlede meldinger har en revisjons-toggle i UI — brukeren kan veksle mellom AI-versjon og original tekst. AI-output rendres som Markdown via marked.

7.1 Meldingsvisning

Lange meldinger (mer enn 2 linjer) kollapses automatisk med en "Vis mer"-knapp. Ved ekspandering vises "Vis mindre" både over og under meldingen, slik at man slipper å scrolle for å kollapse igjen.

8. Tale-til-tekst (Voice-to-text)

Mobilvennlig diktering for situasjoner der tastatur er upraktisk. Brukeren trykker en mikrofon-knapp, snakker, og får teksten tilbake som en vanlig melding klar til redigering og sending.

8.1 Flyt

  1. Bruker trykker og holder (eller toggler) mikrofon-knappen i chat-feltet.
  2. Nettleseren fanger lyd via MediaRecorder API (WebM/Opus).
  3. Lydklippet sendes til Whisper (POST /v1/audio/transcriptions, response_format=text, language=no) via SvelteKit server-side.
  4. Transkripsjonen settes inn i meldingsfeltet — brukeren kan redigere før sending.
  5. Ingen lagring av lydfilen — den kastes etter transkripsjon.

8.2 Avgrensning

  • Dette er ikke en lydmelding-feature (à la WhatsApp). Lyden er et transportmiddel for tekst. Kun teksten lagres.
  • Whisper-kallet er kort (<30 sek tale) og kan rutes direkte til Whisper-serveren uten jobbkø.
  • Bruk small-modellen for lav latens. Navnenøyaktighet er mindre viktig for korte chatmeldinger.

9. TTL (automatisk opprydding)

Channels med config.ttl_days satt til et tall får sine meldinger automatisk slettet av en nattlig jobbkø-jobb. Brukes for flyktige kontekster (scratchpads, studio-chat).

10. Implementeringsstatus

Ferdig (mars 2026)

  • ChatBlock.svelte: Adapter-mønster via createChat() factory. Bruker chat.edit(), chat.delete(), chat.react() — ingen direkte PG API-kall.
  • WebSocket-adapter: Sanntidsdata via PG LISTEN/NOTIFY + WebSocket.
  • Shared types (types.ts): ChatConnection interface med send, edit, delete, react, readonly.
  • Tråder: Komplett trådvisning med datogruppering, autoscroll og visuell skillelinje mellom tråder.
  • Reaksjoner: Lagret i PG, propagert via WebSocket.
  • Meldingskollaps: Lange meldinger begrenses til 2 linjer med "Vis mer"/"Vis mindre".
  • AI-behandling: Meldinger kan AI-behandles (-knapp, eldre modell). Revisjons-toggle viser original vs. AI-versjon. Markdown-rendering for AI-output. NB: Erstattes av frittstående AI-verktøy på arbeidsflaten — se docs/features/ai_verktoy.md.
  • Konvertering: Meldinger kan opprettes som kanban-kort eller kalenderhendelse (dialog sier "Opprett", ikke "Konverter" — meldingen beholdes i chatten).

ChatTrait panel (oppgave 20.5, mars 2026)

  • Inline panel: ChatTrait er nå et fullverdig BlockShell-panel som viser meldinger, input og taleopptak direkte i panelet — ikke bare lenker til /chat/[id].
  • Kanalliste → chatvisning: Ved flere kanaler vises kanalliste, klikk åpner inline chat. Ved én kanal åpnes chatten direkte.
  • BlockReceiver: Aksepterer drops fra alle andre paneler (lettvekts-triage-modus). Droppet innhold knyttes til aktiv kanal.
  • Drag-out: Meldingsbobler er draggable — kan dras til andre paneler (kanban, editor, etc.).
  • Responsivt: Tilpasser seg container-størrelse via flex-layout. Fungerer i både BlockShell-panel (desktop) og mobilfane.
  • Fullskjerm-toggle: Via BlockShell-wrapperen (forelder-side wrapper ChatTrait i BlockShell).
  • /chat/[id]-ruten beholdes som frittstående fullside-visning for direktelenker og deling.

Gjenstår

  • Vedlegg, TTL — avventer implementering.
  • Pin/konvertering: Gjenstår.

11. Instruks for Claude Code

  • Opprettelsesrekkefølge: Opprett nodes-rad → channels-rad → (for meldinger) nodes-rad → messages-rad. Alt i én transaksjon.
  • Channel-opprettelse: Når en Tema, Episode eller Møte opprettes, opprett alltid en default-channel i samme transaksjon.
  • Mentions-parsing: Skjer i sync-workeren ved persistering til PG. Parser mention-UUIDs fra HTML body og oppretter graph_edges.
  • Config-respekt: Frontend-komponenten må lese channel.config og slå av/på UI-elementer.
  • PG er eneste datakilde. Sanntid via LISTEN/NOTIFY + WebSocket.
  • Tilgang styres via node_access-matrisen. Channels arver tilgang fra sin parent-node via edges.