Ny retning: arbeidsflaten.md — spatial canvas med verktøy-paneler. Drag-and-drop mellom verktøy oppretter nye noder med source_material-edges. Noder muterer ikke — de føder nye noder. Oppdatert docs: - universell_input.md: "retyping" → "nye noder fra eksisterende" - rom_ikke_forum.md: "bli" → "føde", siloer → verktøy-paneler - universell_overfoering.md: blokker → verktøy-paneler, dual-modell - meldingsboks.md: multi-rolle → visning i flere kontekster Nye docs: - arbeidsflaten.md: retning med kompatibilitetsmatrise og inkompatibilitet - artikkelverktoy.md: langform TipTap-editor med drag-and-drop mottak Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
17 KiB
Feature: Meldingsboks — universell diskusjonsprimitiv
Filsti: docs/features/meldingsboks.md
1. Konsept
Meldingsboksen er den sentrale byggeklossen for alt ustrukturert innhold i Sidelinja. Én datamodell som erstatter separate tabeller for chat-meldinger, kanban-kort, kalenderhendelser, faktoider og notater. Samme objekt, samme diskusjon, vist i flere kontekster via view-config.
1.1 Hva meldingsboksen erstatter
| Tidligere modell | Egen tabell | Blir nå |
|---|---|---|
| Chat-melding | messages |
Meldingsboks (node) |
| Kanban-kort | kanban_cards |
Meldingsboks + kanban_card_view |
| Kalenderhendelse | calendar_events |
Meldingsboks + calendar_event_view |
| Faktoide | factoids |
Meldingsboks med ABOUT-edge |
| Notat | notes |
Meldingsboks med tittel |
1.2 Hva som IKKE er meldingsbokser
Typed nodes med strukturelt unike skjemaer forblir egne detailtabeller:
| Type | Hvorfor egen tabell |
|---|---|
| Entitet | name, type, aliases, avatar_url — autocomplete, Whisper-prompt, autoritativ navngiving |
| Episode | title, guid (immutabel RSS-krav), published_at |
| Segment | episode_id, start_time/end_time, transcript, FTS-indeks |
Entiteter (erstatter actors + topics) er alt som kan nevnes med #: personer, organisasjoner, steder, temaer, konsepter. Se docs/features/kunnskapsgraf_og_relasjoner.md §3.2.
Typed nodes kan kobles til meldingsbokser via edges for diskusjon. En entitet har ikke innebygd diskusjon, men en meldingsboks kan knyttes til den med en DISCUSSED_IN-edge.
2. Alle meldinger er noder
Hver meldingsboks er en fullverdig node i kunnskapsgrafen (node_type = 'melding'). Ingen vektklasser, ingen promoteringslogikk. Opprettelse er alltid: INSERT INTO nodes + INSERT INTO messages i én transaksjon.
Hvorfor: De fleste meldinger i en aktiv redaksjon ender opp med å trenge graf-tilkobling uansett (mentions, svar, stemmer). Promoteringslogikk legger til kompleksitet uten reell gevinst. nodes-tabellen tåler volumet — TTL rydder opp i flyktige meldinger, og node_type-filter sikrer at spørringer aldri treffer hele tabellen.
Konsekvens: Ethvert svar er en rik entitet som kan kobles til kanban, kalender, graf — full fleksibilitet uten spesialtilfeller. Et svar på tredje nivå i en diskusjon kan bli en kalenderoppføring, og konteksten forsvinner ikke.
3. Datamodell
3.1 Messages (erstatter messages, kanban_cards, calendar_events, factoids, notes)
CREATE TABLE messages (
id UUID PRIMARY KEY REFERENCES nodes(id) ON DELETE CASCADE,
channel_id UUID REFERENCES nodes(id) ON DELETE CASCADE,
reply_to UUID REFERENCES messages(id) ON DELETE SET NULL,
author_id TEXT REFERENCES users(authentik_id) ON DELETE SET NULL,
message_type message_type NOT NULL DEFAULT 'text',
title TEXT, -- Kanban-kort, notater, kalenderhendelser, faktoider
body TEXT NOT NULL,
metadata JSONB, -- Ekstra data per message_type
pinned BOOLEAN NOT NULL DEFAULT false,
visibility TEXT NOT NULL DEFAULT 'shared', -- 'shared' | 'private'
edited_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX idx_messages_channel ON messages(channel_id, created_at);
CREATE INDEX idx_messages_reply ON messages(reply_to) WHERE reply_to IS NOT NULL;
CREATE TRIGGER trg_messages_updated_at BEFORE UPDATE ON messages
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
Forskjeller fra gammel messages-tabell:
ider FK tilnodes(id)— alle meldinger er noder i kunnskapsgrafen- Ingen
workspace_id— tilgang styres vianode_access-matrisen channel_ider nullable — notater og standalone-bokser trenger ikke en channeltitleer førsteklasses felt — brukes av kanban-kort, notater, kalenderhendelserpinnedfor manuell fritak fra TTL-slettingvisibilitystyrer synlighet —'shared'(alle med tilgang vianode_access) eller'private'(kun forfatter). Private meldingsbokser kan brukes som kladd for notater, kanban-kort og kalenderhendelser. Endre til'shared'for å dele.
3.1.1 Kontekst-navigering via reply_to
Når en meldingsboks har roller i flere kontekster (f.eks. et svar som også er en kalenderoppføring), gir reply_to-kjeden alltid vei tilbake til opprinnelig diskusjon:
📅 Kalenderen: "Planleggingsmøte for konferanse"
↩ Fra diskusjon i #Mediepolitikk → "Vi burde kanskje..."
UI-et følger reply_to → forelder → channel_id → parent node for å bygge brødsmulesti. Ingen ekstra data nødvendig — reply_to + view-config gir hele bildet i begge retninger:
- Fra kalenderen: "Hvor kom denne fra?" → følg
reply_tooppover - Fra chatten: "Hva ble dette?" → se at svaret har
calendar_event_view→ vis kalender-badge
3.2 Kanban view-config (erstatter kanban_cards)
CREATE TABLE kanban_card_view (
message_id UUID PRIMARY KEY REFERENCES messages(id) ON DELETE CASCADE,
column_id UUID NOT NULL REFERENCES kanban_columns(id) ON DELETE CASCADE,
position REAL NOT NULL DEFAULT 0,
color TEXT,
assignee_id TEXT REFERENCES users(authentik_id) ON DELETE SET NULL
);
CREATE INDEX idx_kanban_card_view_column ON kanban_card_view(column_id, position);
Et kanban-kort er en meldingsboks + en rad i kanban_card_view. Tittel og beskrivelse lever i messages.title/messages.body. Flytt mellom kolonner = UPDATE kanban_card_view SET column_id = ....
kanban_boards og kanban_columns forblir uendret — de er strukturelle tabeller, ikke grafnoder (kolonner er intern organisering).
3.3 Kalender view-config (erstatter calendar_events)
CREATE TABLE calendar_event_view (
message_id UUID PRIMARY KEY REFERENCES messages(id) ON DELETE CASCADE,
calendar_id UUID NOT NULL REFERENCES calendars(id) ON DELETE CASCADE,
starts_at TIMESTAMPTZ NOT NULL,
ends_at TIMESTAMPTZ,
all_day BOOLEAN NOT NULL DEFAULT false,
color TEXT
);
CREATE INDEX idx_calendar_event_view_calendar ON calendar_event_view(calendar_id, starts_at);
calendars-tabellen forblir uendret.
3.4 Reaksjoner (erstatter factoid_votes og message_votes)
CREATE TABLE message_reactions (
message_id UUID NOT NULL REFERENCES messages(id) ON DELETE CASCADE,
user_id TEXT NOT NULL REFERENCES users(authentik_id) ON DELETE CASCADE,
reaction TEXT NOT NULL, -- 'upvote', 'downvote', '👍', '🔥', etc.
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
PRIMARY KEY (message_id, user_id, reaction)
);
Én tabell for alle interaksjoner — opp/ned-stemmer ('upvote'/'downvote') og emoji-reaksjoner ('👍', '🔥') i samme modell. Ikke graf-edges — reaksjoner er høyfrekvente, lav-semantiske operasjoner.
Sortering etter stemmer: SELECT COUNT(*) FILTER (WHERE reaction = 'upvote') - COUNT(*) FILTER (WHERE reaction = 'downvote') AS score.
3.5 Message revisions (uendret)
CREATE TABLE message_revisions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
message_id UUID NOT NULL REFERENCES messages(id) ON DELETE CASCADE,
body TEXT NOT NULL,
edited_at TIMESTAMPTZ NOT NULL DEFAULT now(),
CONSTRAINT revision_order UNIQUE (message_id, edited_at)
);
3.6 Nye relasjonstyper
Eksisterende relasjonstyper dekker behovene — ingen nye nødvendig:
DISCUSSED_IN— kobler typed nodes til meldingsbokser (diskusjon på en aktør/tema)ABOUT— kobler faktoide-meldinger til aktører/temaerMENTIONS— chat-mentions via#-tags
3.7 node_type enum (opprydding)
Aktive verdier etter migrering:
'entitet'— person, organisasjon, sted, tema, konsept (erstatter'aktør'og'tema')'episode','segment'— typed nodes'melding'— meldingsboks (chat, kanban-kort, kalenderhendelse, faktoide, notat)'channel'— gruppering av meldinger'kanban_board','calendar','meeting'— strukturelle
Utfasede verdier (kan ikke fjernes fra PG ENUM, men skal aldri brukes i ny kode):
'aktør', 'tema', 'faktoide', 'note', 'kanban_card', 'calendar_event'
4. Visning i flere kontekster
En node kan vises i flere verktøy samtidig via edges og view-configs.
For lettvekts-triage (node → kanban, node → kalender) legges en edge/
view-config til. For innholdstransformasjon (chatboble → artikkel)
opprettes en ny node med source_material-edge. Se
arbeidsflaten.
Multi-visning via view-config (samme node, flere kontekster):
Meldingsboks (messages + nodes)
├── kanban_card_view → vises på kanban-brettet
├── calendar_event_view → vises i kalenderen
├── edge: ABOUT → aktør/tema → vises som faktoide
├── edge: DISCUSSED_IN ← typed node → diskusjon på aktør/tema/episode
├── reply_to → parent melding → tråd-kontekst
└── reply_to ← svar → diskusjonstråd følger med i alle kontekster
En meldingsboks kan ha flere roller samtidig: et kanban-kort som også er en kalenderhendelse, med en diskusjonstråd under seg. Svar på meldingsboksen følger med uansett kontekst — diskusjonen er alltid tilgjengelig.
5. Channels
Channels forblir som grupperingsmekanisme. En channel samler meldingsbokser under ett scope — typisk knyttet til en typed node (tema, episode, møte).
Channels opprettes ved behov, ikke automatisk for alle noder. Når en bruker starter en diskusjon på en aktør, opprettes en channel i samme transaksjon.
Channel-config (threads, mentions, attachments, ttl_days) arves fra kontekst (se docs/features/chat.md §2.2).
6. Slette-semantikk: "Fjern" vs "Slett"
En meldingsboks kan ha flere roller (kanban-kort, kalenderhendelse, diskusjonstråd). UI-et må ha et skarpt, eksplisitt skille:
| Handling | Hva skjer | Konsekvens |
|---|---|---|
| Fjern fra brett/kalender | DELETE FROM kanban_card_view / calendar_event_view |
Meldingen lever videre i chatten og grafen. Kun visningen forsvinner. |
| Slett innhold | DELETE FROM messages → cascader til nodes |
Alt borte: diskusjonstråd, view-configs, graf-edges. Irreversibelt. |
UI-regler:
- "Fjern fra brett" / "Fjern fra kalender" — standardhandling i kontekstmeny på kanban/kalender. Trygt.
- "Slett permanent" — bak en bekreftelsesdialog ("Denne meldingen har 3 svar og er koblet til 2 entiteter. Slett alt?"). Viser konsekvensene eksplisitt.
- En melding med aktive roller i andre views bør vise en advarsel: "Denne meldingen er også et kanban-kort i [brett]. Fjern fra brettet først, eller slett alt?"
7. Nesting og utskilling
Maks 3 nivåer visuelt (via reply_to-kjeding):
- Boks (trådstart)
- Svar på boks
- Svar på svar
Ved nivå 3 tilbyr systemet: "Skill ut som egen diskusjon?"
- Svaret promoteres til boks (ny node)
- Originaltråden får en lenke-melding: "→ Diskusjonen fortsetter her"
- Den nye boksen lever sitt eget liv
8. Eierskap, kurasjon og prominens
8.1 Eierskap
Trådstarter og admin for samlings-noden deler eierskap over en diskusjonstråd. Eierskap gir tilgang til kurasjonsverktøy (se 7.2).
8.2 Kurasjon (TODO — UI-features, bygges inkrementelt)
Datamodellen trenger ikke endres for disse — alt håndteres via messages.metadata (JSONB):
- Absorber svar —
metadata.absorbed = true. Svaret kollapses visuelt, ikke slettet. - Kollapser utdaterte svar —
metadata.collapsed = true. Alltid tilgjengelig med klikk. - Fest viktige svar —
metadata.featured = true. Vises prominent i lang tråd.
8.3 Prominens (avledet, ikke lagret)
Hvor viktig en meldingsboks er, beregnes fra eksisterende data — aldri lagret som en score:
- Antall svar (
COUNTpåreply_to) - Stemmer (
SUMframessage_votes) - Antall roller (finnes i
kanban_card_view,calendar_event_view?) - Graf-koblinger (
COUNTfragraph_edges) - Alder på siste aktivitet (
MAX(created_at)fra svar)
Beregnes ved visning eller caches i materialized view ved behov. Algoritmen kan justeres uten migrasjoner eller skjemaendringer.
9. TTL og livsløp
9.1 To-trinns fading
- Skjult fra visning — meldingen forsvinner fra default UI etter TTL (arvet fra channel eller samlings-node).
messages.metadata.hidden_atsettes. - Slettet — etter tilleggsperiode (dobbel TTL) fjernes raden permanent.
9.2 Alder som dynamisk faktor
Tid bidrar til utfasing — eldre meldinger uten aktivitet eller koblinger fader naturlig. Prominens-scoren (§8.3) synker med alder, og TTL-jobben bruker den til å avgjøre hva som skjules og slettes.
9.3 Fritak-regler
En melding slettes ikke hvis:
- Den har graf-edge(s) (
ABOUT,MENTIONS,DISCUSSED_IN, etc.) — koblet til noe varig i kunnskapsgrafen. Dette er det som gjør faktoider immune: enABOUT-edge til en aktør/tema betyr at informasjonen har verdi utover konteksten den ble skrevet i. - Den har
kanban_card_view-rad i en aktiv kolonne - Den har
calendar_event_view-rad med fremtidig tidspunkt - Den har aktive svar (siste svar innenfor TTL)
pinned = true
Prinsippet: Grafen bestemmer hva som er varig. Ingen spesialhåndtering for faktoider — enhver melding med en graf-kobling overlever. Meldinger uten koblinger fader med tid.
Lever boksen, lever alt under den — svar beholdes uansett alder.
9.4 Konfigurerbarhet
Samlings-node default TTL: 30 dager (samlings-node metadata.default_ttl_days)
└── Channel kan overstyre: config.ttl_days
└── Individuelle meldinger frittes via reglene over
10. <MessageBox> Svelte-komponent
Én komponent som rendrer en meldingsboks i alle kontekster:
- Kompakt modus — kanban: tittel + "3 svar" + fargekode
- Kalender-modus — tittel + tidspunkt + fargekode
- Utvidet modus — full diskusjon med innrykk (maks 3 nivåer)
- Leser kontekst og tilpasser capabilities (stemmer, mentions, vedlegg)
- Lazy-loader tråd ved expand (ytelse)
11. Konsekvenser for eksisterende kode
11.1 Tabeller som fjernes
kanban_cards→ erstattes avkanban_card_viewcalendar_events→ erstattes avcalendar_event_viewfactoids+factoid_votes→ erstattes avmessages+message_reactionsmessage_votes→ erstattes avmessage_reactionsnotes→ erstattes avmessagesmed tittel
11.2 Tabeller som forblir uendret
kanban_boards,kanban_columns— strukturellecalendars— strukturellchannels— grupperingmessage_revisions— revisjonshistorikkmessage_attachments,media_files— vedlegg
11.3 API-endringer
Eksisterende API-ruter (/api/kanban/, /api/calendar/, /api/notes/) refaktoreres til å bruke den nye datamodellen. Grensesnittet mot frontend kan holdes stabilt — endringene er i datalaget.
12. Migrering (0005_meldingsboks.sql)
Migrasjonen konverterer eksisterende data:
- Endre
messages-tabellen: Legg tiltitle,pinned.id → nodes(id)beholdes (alle meldinger er allerede noder). - Migrer kanban-kort: For hver
kanban_cards-rad: opprettmessages-rad (medtitle,bodyfra description, gjenbruk eksisterende node-id) +kanban_card_view-rad. - Migrer kalenderhendelser: For hver
calendar_events-rad: opprettmessages-rad +calendar_event_view-rad. - Migrer faktoider: For hver
factoids-rad: opprettmessages-rad (medbody,message_type = 'factoid'). Flyttfactoid_votestilmessage_votes. OpprettABOUT-edges igraph_edges. - Migrer notater: For hver
notes-rad: opprettmessages-rad (medtitle,bodyfra content). - Drop gamle tabeller:
kanban_cards,calendar_events,factoids,factoid_votes,notes.
Merk: Migrasjonen bør ha en tilhørende down-migrering som gjenskaper de gamle tabellene og flytter data tilbake.
13. Instruks for Claude Code
- Opprettelse av meldingsboks: Alltid INSERT i
nodes(type 'melding') + INSERT imessagesmed samme id. Alt i én transaksjon. - Kanban-kort: INSERT i
nodes+messages(med tittel) +kanban_card_view. Én transaksjon. - Kalenderhendelse: INSERT i
nodes+messages(med tittel) +calendar_event_view. Én transaksjon. - Faktoide: INSERT i
nodes+messages(message_type = 'factoid') +graph_edgesmedABOUT-relasjon til aktør/tema. Én transaksjon. - Notat: INSERT i
nodes+messages(med tittel + body).channel_idpeker på en personlig eller delt channel, eller er NULL. - Visning i ny kontekst: Legg til view-config-rad (kanban/kalender) for lettvekts-triage. For innholdstransfer: ny node med
source_material-edge. - Kontekst-navigering: Bruk
reply_to-kjeden for å bygge brødsmulesti tilbake til opprinnelig diskusjonskontekst. - TTL: Implementér som nattlig jobbkø-jobb (
message_ttl_cleanup). Sjekk fritak-regler før sletting. Ved sletting: slett framessages→ cascade tilnodesvia FK. - Tilgang: Styres via
node_access-matrisen. Visibility håndteres i applikasjonskode (SvelteKit):WHERE visibility = 'shared' OR author_id = $current_user. - Visibility: Default
'shared'. Sett'private'for personlige kladder. Endre til'shared'for å dele — ingen kopiering nødvendig.