# Forslag: Universell editor

> **Delvis implementert:** TipTap-editoren er implementert (Fase 3.5, 14,
> 20.8) med kompakt og utvidet modus, mentions, markdown-formatering og
> auto-save. Aspirerende features (collaborative editing/Yjs, LaTeX/KaTeX,
> podcast-embeds, sidemerknad-fotnoter) er fremtidige utvidelser.
> AI-behandling er flyttet til eget verktøy-panel (`docs/features/ai_verktoy.md`).

## Idé
Én editor-komponent som brukes overalt i Sidelinja — chat, notater, artikler, kanban-kort, show notes. Editoren autodetekterer format (plaintext, markdown, LaTeX) og rendrer riktig uten at brukeren velger modus. Avansert funksjonalitet er alltid tilgjengelig, aldri påtvunget.

## Kjerneprinsipp: Brukeren bare skriver

Editoren forstår hva brukeren skriver og rendrer det riktig — live, uten konfigurasjon:

```
Bruker skriver              Autodetektert           Rendres som
──────────────              ─────────────           ───────────
hei                         plaintext               tekst
**viktig**                  markdown                bold
$E = mc^2$                  LaTeX inline            formel
$$\int_0^1 f(x)dx$$        LaTeX blokk             sentrert formel
```python\n...              kodeblokk               syntax highlight
# Overskrift               markdown heading         H1
#Skolepolitikk              mention                 graf-edge + lenke
{{segment:uuid}}            podcast-embed           lydspiller
bilde dratt inn             media                   inline bilde med bildetekst
https://youtu.be/xyz        YouTube-embed           innebygd videospiller
https://example.com         lenke                   rik forhåndsvisning (OG-kort)
```

En bruker som kan markdown bruker markdown. En bruker som kan LaTeX bruker LaTeX. En bruker som bare skriver vanlig tekst får vanlig tekst. Ingen modusvelger, ingen forhåndsvalg.

## Hvorfor er dette et eget prosjekt?

Editoren er det mest komplekse enkeltkomponenten i Sidelinja:
- Autodeteksjon av formater (markdown, LaTeX, mentions, embeds)
- Progressiv toolbar (fra usynlig til fullverdig)
- Live rendering av alt innhold
- `#`-mention med autocomplete og graf-integrasjon
- Podcast-embeds, bilder, vedlegg
- Versjonering og auto-save
- Format-kontekstsensitivitet (emoji i chat vs sirlig typografi i artikler)
- Fremtidig: collaborative editing (Yjs)
- Mobilopplevelse

Alt dette fortjener sin egen spec, sin egen utviklingssyklus, og sin egen iterasjon — uavhengig av tekst-primitiven (arkitektur) og artikkel-publisering (distribusjon).

## Hva bygger den på?
- **Tekst-primitiv** — filosofien om at enhver melding kan vokse
- **Meldingsboks** — datamodellen editoren skriver til (`messages.body`)
- **Kunnskapsgraf** — `#`-mentions oppretter graf-edges
- **Message revisions** — editoren trigge lagring av revisjoner

## Skisse

### Teknologivalg: Tiptap (ProseMirror)

Tiptap er det naturlige valget for SvelteKit:
- ProseMirror-basert, modular, godt vedlikeholdt
- Headless — full kontroll over UI og toolbar
- Lagrer som JSON (strukturert, transformerbart)
- Utvidbar med custom nodes og marks
- Svelte-kompatibelt (`@tiptap/core` headless + egen Svelte-wrapper)
- Collaborative editing via Yjs-plugin (fase 2)

### Progressiv toolbar

Editoren er **én komponent** (`<Editor>`) med ulik toolbar-konfigurasjon basert på kontekst:

**Kompakt** (default i chat, kanban-kort, quick reply):
```
[tekst-input .......................... ↑] [Send]
```
Ingen synlig toolbar. `#`-mentions og inline-formatering (bold, italic, lenker) via keyboard shortcuts. Enter = send. `↑`-knappen utvider til full modus.

**Utvidet** (notater, lengre tekster, "↑" fra kompakt):
```
[Tittel                                            ]
[B I S ~ | H1 H2 H3 | • — ✓ | 🔗 📎 # | ↓ ]
[                                                  ]
[ editor-innhold med live-rendering                ]
[                                                  ]
```
Full toolbar. Enter = ny linje. Auto-save. `↓` kollapser tilbake.

**Publisering** (artikler med `article_view`):
```
[Tittel                                            ]
[B I S ~ | H1 H2 H3 | • — ✓ | 🔗 📎 # | ƒ 🎙️ |📝]
[                                                  ]
[ editor-innhold med sidemerknad-støtte            ]
[                                                  ]
[Slug: ________] [Status: Utkast ▼] [Forhåndsvis] [Publiser]
```
Alt fra utvidet + LaTeX-toolbar, podcast-embeds, fotnoter, publiseringskontroller. `📝`-knappen åpner sidemerknad-panel.

**Modusene er ikke låst.** Brukeren kan alltid utvide eller kollapse. Toolbar-nivå er en UI-preferanse per kontekst, ikke en datadistinksjon.

### Raw / Rendered — brukeren bestemmer

Editoren har to visninger, alltid tilgjengelig via en enkel toggle (Ctrl+/ eller knapp i toolbar):

**Raw:** Viser kildekoden. Markdown som markdown, LaTeX som LaTeX, mentions som `#Skolepolitikk`. En monospace-editor der du ser nøyaktig hva som er lagret. Ingen magi, ingen overraskelser. Syntax highlighting for lesbarhet, men ingen transformasjon av det du skriver.

```
# Min analyse av skolepolitikken

Gini-koeffisienten $G = \frac{1}{2n^2\bar{x}}$ viser at...

**Viktig:** Se også #Utdanningsforbundet sin rapport.

{{segment:550e8400-e29b-41d4-a716-446655440000}}
```

**Rendered:** WYSIWYG-visning. Overskrifter er store, bold er bold, LaTeX er rendret som formler, mentions er klikkbare lenker, podcast-embeds er lydspillere. Du kan skrive direkte i denne visningen — toolbar-knapper og formatering fungerer som i et vanlig tekstbehandlingsverktøy.

```
┌──────────────────────────────────────────────────┐
│ Min analyse av skolepolitikken          [Raw/Rendered]
│                                                  │
│ Gini-koeffisienten G = ½n²x̄ viser at...         │
│                                                  │
│ Viktig: Se også Utdanningsforbundet sin rapport. │
│                                                  │
│ ▶ [Episode 42, 14:23–21:07] ░░░░░░░░            │
└──────────────────────────────────────────────────┘
```

**Prinsippet:** Rendered er standard for de fleste. Raw er alltid ett tastetrykk unna for de som vil ha kontroll. Begge visningene redigerer samme data — switch er øyeblikkelig og tapsfri.

**Teknisk:** Raw-modus er en CodeMirror-instans (eller enkel textarea med syntax highlighting) som opererer på serialisert markdown/LaTeX. Rendered-modus er Tiptap WYSIWYG. Begge skriver til samme Tiptap JSON — raw via en markdown→JSON parser, rendered direkte. Switch mellom dem re-serialiserer.

**Viktig detalj:** I rendered-modus forsvinner ikke kildekoden. Klikker du på en rendret formel, ser du LaTeX-kilden inline (som Obsidian gjør med markdown). Klikker du utenfor, rendres den igjen. Rendered-modus er altså ikke et "preview" — det er en visuell editor der kildekoden er tilgjengelig ved klikk.

### Autodeteksjon

Editoren forstår hva brukeren skriver — i begge moduser:

**Markdown:** `**bold**`, `# Heading`, `- item`, `> quote`, `` `code` ``. I raw: syntax highlighted. I rendered: rendret som formatering.

**LaTeX:** `$...$` inline, `$$...$$` blokk. I raw: syntax highlighted. I rendered: rendret med KaTeX.

**Mentions:** `#` trigger autocomplete i begge moduser. Ved valg opprettes en mention-node som rendres som lenke (rendered) eller `#Navn` (raw), og oppretter `MENTIONS`-edge ved lagring.

**Podcast-embeds:** `{{segment:uuid}}`. I raw: syntax highlighted. I rendered: mini-lydspiller.

**Kodeblokker:** Triple backtick med språk-hint. Syntax highlighting i begge moduser.

### Media, lenker og embeds

Editoren håndterer bilder, lenker og eksterne embeds som førsteklasses innhold — ikke som vedlegg på siden, men inline i teksten.

**Bilder:**
- Drag-and-drop, paste fra utklippstavle, eller opplastingsknapp i toolbar
- Lastes opp til `media_files` (eksisterende tabell) via API
- Inline i teksten med valgfri bildetekst og alt-tekst
- Resize ved å dra i hjørnet (rendered-modus)
- I raw: `![alt-tekst](media:uuid)` eller standard markdown bildesyntaks
- Responsiv `srcset` genereres ved opplasting (jobbkø) for publiserte artikler

**Lenker:**
- Paste en URL → autodetekteres som lenke
- Rik forhåndsvisning (Open Graph): tittel, beskrivelse, miniatyrbilde — hentes asynkront ved paste, caches
- Brukeren kan velge mellom rik forhåndsvisning (kort) og enkel tekstlenke
- I raw: standard markdown `[tekst](url)` eller bare URL

**Externe embeds:**
- YouTube, Vimeo, Twitter/X, o.l. — paste en URL, editoren gjenkjenner domenet og rendrer innebygd spiller/visning
- Basert på oEmbed-protokollen der tilgjengelig, ellers sandboxed iframe
- I raw: bare URL-en på egen linje. I rendered: innebygd player med riktig aspekt-ratio
- Whitelist-basert: kun kjente domener får embed-behandling. Ukjente URL-er vises som rik lenke-forhåndsvisning

**Filvedlegg:**
- PDF, dokumenter, andre filer — lastes opp til `media_files`, vises som nedlastbar lenke med filtype-ikon og størrelse

**Teknisk:**
- Alle opplastinger går via `POST /api/media` → lagres content-addressable i `media_files`
- Referanser i Tiptap JSON er `media:uuid` — aldri direkte fil-URL-er. Gjør det mulig å flytte lagring (lokal → CDN) uten å endre innhold
- Bildeoptimalisering (resize, WebP-konvertering) som jobbkø-oppgave ved opplasting
- oEmbed/OG-metadata caches i en enkel tabell for å unngå gjentatte oppslag

### AI-behandling — eget verktøy-panel

> **Oppdatert:** AI-behandling er flyttet fra en ✨-knapp inne i editoren
> til et **frittstående AI-verktøy** på arbeidsflaten. Se
> `docs/features/ai_verktoy.md` for full spesifikasjon. Drag-and-drop
> mellom AI-verktøyet og tekstnoder erstatter den opprinnelige ✨-knappen.
> Teksten nedenfor er bevart som historisk referanse.

Editoren har en AI-knapp (✨) som behandler innholdet i boksen. Originalteksten bevares alltid som revisjon (`message_revisions`), og AI-resultatet tar over som nytt innhold — klart for videre redigering av brukeren.

Det som opprinnelig var tenkt som en separat "AI Research-Klipper"-modal er nå bare én av handlingene her: paste inn hva som helst → trykk ✨ → AI-en behandler det.

**Flyten:**
```
Bruker limer inn rotete Ctrl+A-tekst fra en nettavis
  → Trykker ✨ (standard: "Fiks tekst")
  → Originalen lagres som revisjon (alltid tilgjengelig)
  → AI-resultatet erstatter innholdet i editoren
  → Brukeren redigerer videre, legger til tittel, justerer
  → AI-en foreslår #-mentions basert på innholdet → graf-edges opprettes
  → Meldingen lever videre: kan få prominens i avisen, bli et kanban-kort, publiseres
```

**Alternativt:** Brukeren kan velge at resultatet publiseres som *ny melding* (svar på originalen) i stedet for å erstatte innholdet. Nyttig for "trekk ut fakta" der originalen og resultatet er to ulike ting.

### Standard-prompt: "Fiks tekst" (✨)

Standardhandlingen — den brukeren får ved å trykke ✨ uten å åpne menyen — er en generisk "magi"-prompt:

```
Fiks denne teksten. Output på norsk.
- Fiks skrivefeil og grammatikk
- Start med en kort oppsummering av det viktigste (2–3 setninger)
- Fjern metainformasjon, navigasjon, annonser og annen støy fra innlimt webinnhold
- Dersom det er tydelig hva kilden er, oppgi den etter innledende oppsummering
- Behold saklig innhold og fakta intakt
- Bruk markdown-formatering der det gir bedre lesbarhet
```

Denne prompten kan justeres per workspace i Prompt Lab (se `docs/features/prompt_lab.md`). Men standarden skal være god nok til at brukeren bare kan lime inn og trykke ✨ uten å tenke.

### Handlingsmeny (lang-trykk eller ▼ ved siden av ✨)

For mer spesifikke behov åpnes en meny:

| Handling | Hva AI-en gjør |
|---|---|
| ✨ Fiks tekst (standard) | Rens, oppsummer, fiks feil, identifiser kilde |
| Trekk ut fakta | Identifiserer påstander, tall, sitater — som separate faktoider (ny melding) |
| Skriv om for publisering | Omskriver til artikkelformat med tittel, ingress, struktur |
| Oversett | Oversetter til valgt språk |
| Custom | Brukerens egne prompts fra Prompt Lab |

### Revisjon og sporbarhet

Når ✨ brukes:
1. Nåværende innhold lagres i `message_revisions` (originalen er alltid tilgjengelig)
2. AI-resultatet erstatter `messages.body`
3. `messages.metadata` oppdateres med `{ ai_processed: true, ai_action: 'fix_text', ai_prompt_id: '...' }`
4. Brukeren ser resultatet i editoren og kan redigere videre, angre (gå tilbake til revisjon), eller kjøre ✨ igjen

Revisjonshistorikken viser tydelig hva som var original og hva som er AI-behandlet. Brukeren kan alltid gå tilbake.

### Teknisk
- `POST /api/ai/process` med `{ message_id, action, prompt_id? }`
- Oppretter jobbkø-oppgave (`ai_text_process`)
- Rust-worker sender til AI Gateway (`http://ai-gateway:4000/v1`)
- Ved "erstatt innhold": lagre revisjon + oppdater `messages.body`
- Ved "ny melding": opprett ny melding med `reply_to = original_message_id`
- AI-foreslåtte `#`-mentions vises for bruker-godkjenning før edges opprettes

### Kontekst-bevisst (Agentic RAG)
AI-en kjenner konteksten meldingen lever i. Hvis den er i en channel knyttet til #Skolepolitikk, brukes det som hint for å identifisere relevante entiteter og fakta. Workspace-kontekst + graf-nabolag gir bedre resultater enn en kontekstløs prompt.

**RAG-berikelse via kunnskapsgrafen:** Når brukeren skriver et utkast og nevner `#Skolepolitikk`, gjør SvelteKit et usynlig vektorsøk (pgvector) i bakgrunnen mot kunnskapsgrafen *før* prompten sendes til AI Gateway. System-prompten oppdateres dynamisk:

```
Sidelinja har tidligere etablert disse faktaene om Skolepolitikk:
- [Faktoide 1 fra grafen]
- [Faktoide 2 fra grafen]
- [Relatert segment fra Episode 42]
Ta hensyn til dette i behandlingen.
```

Resultatet: AI-en ikke bare retter skrivefeil, men fyller inn kontekst spesifikk for redaksjonens kunnskapsbase. Krever pgvector-migrasjon (0006) og `generate_embeddings`-jobbtype.

### Format-kontekst

Ikke alt passer overalt. En emoji-rik chatmelding og en sirlig publisert artikkel har ulike estetiske forventninger:

**Chat-kontekst:** Alt tillatt. Emojis, GIFs, korte meldinger, uformelt.

**Publiserings-kontekst:** Editoren tilbyr et "publiseringsfilter" — en valgfri siste-sjekk som flagger potensielle stilbrudd (emojis i overskrifter, manglende alt-tekst på bilder, etc.). Aldri blokkerende — bare forslag.

Publiseringskonteksten tilbyr en "forhåndsvisning som leser" der teksten rendres i den ferdige typografi-stacken (Literata, marginer, sidemerknad-fotnoter) slik at forfatteren ser hvordan det blir. Dette er en tredje visning — read-only, ren leseopplevelse — tilgjengelig via [Forhåndsvis]-knappen i publiserings-modus.

### Brukerinnstillinger

Skriftstørrelse, linjehøyde, font, tema og andre visuelle preferanser styres per bruker. Se `docs/features/brukerinnstillinger.md` for full spec — inkludert datamodell (`users.settings` JSONB), CSS custom properties, innstillingspanel og editor-spesifikke preferanser (standard Raw/Rendered, stavekontroll, tegnteller).

### Lagringsformat

**Tiptap JSON** som universelt format:

```json
{
  "type": "doc",
  "content": [
    { "type": "paragraph", "content": [
      { "type": "text", "text": "Gini-koeffisienten: " },
      { "type": "math_inline", "attrs": { "latex": "G = \\frac{1}{2n^2\\bar{x}}" } }
    ]},
    { "type": "mention", "attrs": { "id": "uuid", "label": "Skolepolitikk" } }
  ]
}
```

En enkel "hei" og en 3000-ords artikkel med LaTeX bruker samme format.

**Body-strategi:**
```
messages.body        → Tiptap JSON (universelt kildeformat)
messages.metadata    → { body_html: '...', body_format: 'tiptap' }
```

**Bakoverkompatibilitet:** Eksisterende ren-tekst-meldinger (der `body` ikke er gyldig JSON) tolkes som plaintext. Editoren wrapper dem i Tiptap-paragraph ved redigering. Ingen migrering — bare fallback i lesekoden.

**Pre-rendret HTML:** `body_html` beregnes ved lagring. Brukes for:
- Rask visning i feeds og lister (ingen klient-parsing)
- Publiserte artikler (KaTeX ferdig-rendret, ingen JS for lesere)
- RSS/Atom-feeds
- Søkeindeksering

### Auto-save og versjonering

**Auto-save:** 500ms debounce etter siste tastetrykk (identisk med dagens notat-mønster). Visuell feedback: "Lagrer..." → "Lagret [tidspunkt]".

**Versjonshistorikk:** `message_revisions` lagrer `body` ved hver lagring (eller ved signifikant endring — delta-basert for å unngå å lagre hvert tastetrykk). Brukeren kan bla gjennom tidligere versjoner og tilbakestille.

**Fremtidig:** Navngitte snapshots ("Kladd 1", "Sendt til review", etc.) via `metadata` på revisjonen. Ikke dag 1.

### Keyboard shortcuts

Konsistente overalt:
```
Ctrl+B          → bold
Ctrl+I          → italic
Ctrl+K          → lenke
Ctrl+Shift+M    → math (LaTeX-blokk)
#               → mention-autocomplete
Tab/Shift+Tab   → innrykk i lister
```

**Enter-oppførsel:**
- Kompakt modus: Enter = send. Shift+Enter = linjeskift.
- Utvidet/publisering: Enter = ny linje. Ctrl+Enter = eksplisitt lagre (auto-save gjør det uansett).

**Toggle:**
- Ctrl+/ → switch mellom Raw og Rendered.

### Lazy loading av extensions

Kompakt modus laster bare:
- Plaintext
- Mentions (`#`-autocomplete)
- Inline formatting (bold, italic, lenke)

Ved Expand lastes:
- Overskrifter, lister, blokk-quotes
- Bilder, vedlegg
- Kodeblokker med syntax highlighting

Ved publiserings-modus lastes:
- KaTeX (LaTeX-rendering)
- Podcast-embeds
- Sidemerknad-fotnoter

God for bundle-størrelse og oppstartstid.

## Åpne spørsmål

### Tekniske
- **Tiptap JSON vs Markdown som kildeformat?** JSON er editor-vennlig. Markdown er portabelt. Anbefaling: JSON som primær, Markdown-import/-eksport som transformasjon.
- **Ytelse:** Tiptap JSON for millioner av chatmeldinger? ~60 bytes overhead per melding. Trolig neglisjerbart, men verdt å måle.
- **KaTeX i editoren:** Live-rendering av LaTeX krever KaTeX lastet i editoren. ~300KB gzipped. Akseptabelt for utvidet/publisering, for mye for kompakt? Lazy load løser det.
- **Collaborative editing:** Tiptap + Yjs er veletablert. Ikke dag 1. Auto-save + `message_revisions` + optimistic locking (`updated_at`) er nok initialt.

### UX
- **Overgangen kompakt → utvidet:** Hvordan føles det? Smooth animasjon? Teksten forblir, toolbar glir inn? Eller instant switch?
- **Autodeteksjon av LaTeX i kompakt modus:** Rendres `$E=mc^2$` i en kort chatmelding? Ja — rendring er universell, toolbar er kontekstbetinget.
- **Mobile:** Toolbar på liten skjerm? Trolig: floating toolbar som dukker opp ved tekstseleksjon (Medium-stil) i stedet for fast toolbar.
- **Paste fra eksterne kilder:** Paste av HTML (fra nettside), Markdown (fra Obsidian), ren tekst? Tiptap håndterer HTML-paste. Markdown-paste krever custom paste handler.

### Format-kontekst
- **Emoji-filtrering i publisering:** For rigid? Brukeren bør ha full frihet. Kanskje bare en visuell advarsel i forhåndsvisning, aldri blokkering.
- **Ulike typografi-profiler?** En personlig blogg kan ha annen estetikk enn et magasin. Tema per publikasjon (se artikkel-publisering)?

## Innsats: Middels–Stor
Tiptap-integrasjon er rett frem. Autodeteksjon, progressiv toolbar, mentions, LaTeX, podcast-embeds, auto-save, versjonering, mobilopplevelse — summen er betydelig. Bør bygges inkrementelt:

1. **Fase 1:** Tiptap med plaintext + mentions + markdown formatting. Kompakt og utvidet modus. Auto-save.
2. **Fase 2:** LaTeX (KaTeX), kodeblokker, bilder/vedlegg. Publiserings-modus.
3. **Fase 3:** Podcast-embeds, sidemerknad-fotnoter, collaborative editing (Yjs).

## Wow-faktor: Høy
En editor der du bare skriver — markdown rendres automatisk, LaTeX rendres automatisk, mentions oppretter graf-koblinger, og alt kan vokse til en publisert artikkel — er en opplevelse de fleste verktøy ikke tilbyr. Det nærmeste er Notion, men uten graf-integrasjon og uten podcast-embeds.

## Relasjon til andre proposals og features
- **Tekst-primitiv** — filosofien editoren realiserer
- **Artikkel-publisering** — publiseringslaget som bruker editoren
- **Personlig workspace** — konteksten der editoren brukes daglig
- **Meldingsboks** (feature) — datamodellen editoren skriver til
- **Komponerbare sider** — maximize gir editoren plass til å gå fra chatboks til fullskjerm skriveverksted
- **Chat** (feature) — kompakt modus erstatter dagens chat-input
- **Notater** (feature) — utvidet modus erstatter dagens textarea
