synops/docs/proposals/editor.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

20 KiB
Raw Blame History

Forslag: Universell editor

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:2321: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 — universell knapp

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 (23 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:

{
  "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: MiddelsStor

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