Commit graph

322 commits

Author SHA1 Message Date
2ac0ba5fcb Starter oppgave 14.3 2026-03-18 00:54:18 +00:00
e050612dec HTML-rendering av enkeltartikler til CAS med SEO-metadata (oppgave 14.2)
Implementerer rendering-pipeline: metadata.document (TipTap JSON) → HTML
via Tera-templates → CAS-lagring → metadata.rendered oppdateres.

Nye moduler:
- tiptap.rs: Konverterer TipTap/ProseMirror JSON til HTML. Støtter
  paragraph, heading, blockquote, lister, code_block, image, hr,
  og marks (bold, italic, strike, code, link, underline).
  XSS-sikker med HTML-escaping.

- render_article jobb i jobbkøen: Henter node + samling, konverterer
  document → HTML, rendrer med Tera + tema, lagrer i CAS, oppdaterer
  nodens metadata.rendered med html_hash og renderer_version.

Endringer:
- publishing.rs: SeoData-struct med OG-tags, canonical URL, JSON-LD.
  render_article_to_cas() for full pipeline. serve_article() serverer
  fra CAS (immutable cache) hvis pre-rendret, fallback til on-the-fly.
  RENDERER_VERSION=1 for fremtidig bulk re-rendering.

- intentions.rs: Trigger render_article-jobb automatisk når belongs_to
  edge opprettes til samling med publishing-trait.

- Alle 4 artikkel-templates: SEO-block med meta description, OG-tags
  (type, title, description, url, site_name, image, published_time),
  canonical URL, RSS-link, og JSON-LD structured data.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 00:52:58 +00:00
5d14acf921 Starter oppgave 14.2 2026-03-18 00:43:12 +00:00
4b9f520eab Tera-templates: innebygde temaer for publisering (oppgave 14.1)
Implementerer publiseringsmotoren med fire innebygde temaer:
- Avis: multi-kolonne, informasjonstung, hero+sidebar+rutenett
- Magasin: store bilder, luft, editorial, cards-layout
- Blogg: enkel, én kolonne, kronologisk liste
- Tidsskrift: akademisk, tekstdrevet, nummerert innholdsfortegnelse

Hvert tema har artikkelmal + forside-mal som Tera-templates (Jinja2-like).
CSS-variabler for theme_config-overstyring fra publishing-traiten —
fungerer meningsfullt med bare "theme": "magasin" (null konfigurasjon).

Teknisk:
- publishing.rs: Tera engine, render-funksjoner, DB-spørringer, HTTP-handlers
- Templates innebygd via include_str! (kompilert inn i binæren)
- Ruter: GET /pub/{slug} (forside), /pub/{slug}/{id} (artikkel),
  /pub/{slug}/preview/{theme} (forhåndsvisning med testdata)
- 6 enhetstester for CSS-variabler, rendering og tema-fallback

Ref: docs/concepts/publisering.md § "Temaer"

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 00:41:54 +00:00
260d3d030c Starter oppgave 14.1 2026-03-18 00:33:54 +00:00
51eb089f0c Trait-administrasjon: admin-UI for å administrere traits på samlinger (oppgave 13.4)
Legger til et admin-panel på samlingssiden der man kan legge til/fjerne
traits fra katalogen og konfigurere per-trait-innstillinger. Endringene
sendes via updateNode til maskinrommet som validerer mot VALID_TRAITS.

- Ny updateNode-funksjon i api.ts
- Delt trait-katalog i lib/traits.ts (brukes av collection/new og TraitAdmin)
- TraitAdmin.svelte: toggle traits, rediger config-nøkler, lagre
- Integrasjon i collection/[id] med Traits-knapp i header

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 00:32:31 +00:00
b05b4f6e58 Starter oppgave 13.4 2026-03-18 00:27:29 +00:00
427eae9641 Pakkevelger: UI for ny samling med pakker eller manuelt trait-valg (oppgave 13.3)
Ny side /collection/new med:
- 13 forhåndsdefinerte pakker (nettmagasin, podcaststudio, redaksjon osv.)
  som kort-grid med ikon, beskrivelse og trait-liste
- Manuelt trait-valg med hele trait-katalogen kategorisert i 9 grupper
- Oppsummering med valgte traits og opprett-knapp
- Navigerer til /collection/[id] etter opprettelse

Knapp «Ny samling» lagt til i mottak-headeren.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 00:26:17 +00:00
7079759c24 Starter oppgave 13.3 2026-03-18 00:21:52 +00:00
263f63bec8 Trait-aware frontend: samlingssider med dynamiske trait-paneler (oppgave 13.2)
Samlingsnoder med `metadata.traits` rendres nå som egne sider på
/collection/[id]. Hvert trait-navn mappes til en dedikert Svelte-komponent
som viser relevant UI. Traits uten egen komponent vises med et generisk panel.

Komponenter for 9 traits: editor, chat, kanban, podcast, publishing,
rss, calendar, recording, transcription. Mottak-siden viser traits som
pills og lenker til samlingssiden.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 00:20:35 +00:00
a404751fe4 Starter oppgave 13.2 2026-03-18 00:15:34 +00:00
37cd133e1d Trait-validering for samlingsnoder (oppgave 13.1)
Maskinrommet validerer nå metadata.traits ved create_node og update_node
for collection-noder. Ukjente trait-navn avvises med 400 Bad Request.

Lukket katalog med 48 gyldige traits fra docs/primitiver/traits.md.
Konfigurasjon per trait er fri JSONB — kun nøkkelnavnene valideres.
Noder som ikke er collections valideres ikke (traits ignoreres).

Inkluderer 6 unit-tester for valideringsfunksjonen.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 00:14:21 +00:00
a21da84122 Starter oppgave 13.1 2026-03-18 00:10:25 +00:00
e5c7791dfc Podcast-RSS: samlings-node med publiserings-edges → generert RSS-feed (oppgave 11.4)
Nytt endepunkt GET /pub/{slug}/feed.xml som genererer RSS 2.0 eller
Atom 1.0 feed for samlinger med rss-trait. Feeden er offentlig (ingen auth).

- Slår opp samling via publishing.slug i metadata.traits
- Henter belongs_to-edges (publiserte noder), sortert på publish_at
- Podcast-samlinger (med podcast-trait) inkluderer <enclosure>-tags
  med CAS-URL, MIME-type og filstørrelse fra has_media-edges
- Støtter RSS 2.0 (default) og Atom 1.0 via rss.format config
- iTunes-namespace for podcast-feeds
- Stabile GUID-er basert på node UUID
- 5 min cache (Cache-Control: public, max-age=300)

Manuell XML-generering uten ekstra avhengigheter — enklere enn å
introdusere en RSS-crate for dette omfanget.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 00:09:10 +00:00
da988234dd Starter oppgave 11.4 2026-03-18 00:03:43 +00:00
6d916d9860 Pruning-logikk: TTL per modalitet, signaler, disk-nødventil (oppgave 11.3)
Implementerer automatisk opprydding av CAS-filer basert på dokumentert
spec i docs/retninger/maskinrommet.md:

- TTL per modalitet: lyd 30d, bilde 30d, video 14d, tekst aldri
- Signaler som forlenger levetid: publishing-edge, siste tilgang
  (last_accessed_at), utranskribert lyd beholdes
- Tre-trinns disk-nødventil:
  - >85%: slett generert innhold (TTS osv, kan regenereres)
  - >90%: aggressiv pruning med kraftig redusert TTL
  - >95%: kritisk — alt uten publishing-edge slettes
- Periodisk bakgrunnsloop: hvert 6. time, oftere ved høy disk
- Tilgangslogging: serving oppdaterer last_accessed_at (fire-and-forget)
- Pruning-hendelser logges til resource_usage_log

Ny modul: maskinrommet/src/pruning.rs
Ny migrasjon: 010_pruning.sql (last_accessed_at kolonne + indeks)
CasStore utvidet med delete(), disk_usage_bytes(), disk_usage_percent()

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 00:02:27 +00:00
70fb5eb141 Starter oppgave 11.3 2026-03-17 23:55:59 +00:00
445f32de69 Sanntidslyd: kommunikasjonsnode → LiveKit-rom (oppgave 11.2)
Kobler kommunikasjonsnoder til LiveKit for sanntidslyd.
Bruker sender join_communication-intensjon, maskinrommet validerer
tilgang og returnerer signert LiveKit JWT-token + rom-URL.

Nye komponenter:
- maskinrommet/src/livekit.rs: JWT token-generering (HS256-signert
  med LIVEKIT_API_SECRET, 1-times TTL, publisher/subscriber-roller)
- POST /intentions/join_communication: validerer deltaker-edge,
  genererer token, oppretter rom i STDB, oppdaterer node-metadata
- POST /intentions/leave_communication: fjerner deltaker fra STDB
- POST /intentions/close_communication: stenger rom (krever owner)
- SpacetimeDB: live_room + room_participant tabeller for sanntids
  deltakerliste (frontend abonnerer via WebSocket)

SpacetimeDB-modul publisert som synops-v2 (ny identitet etter
at den opprinnelige ikke lenger var tilgjengelig). .env oppdatert.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 23:54:40 +00:00
5392560807 Starter oppgave 11.2 2026-03-17 23:42:13 +00:00
e95b7d6663 TTS-pipeline: tekst → lyd via ElevenLabs (oppgave 10.4)
Ny jobbtype `tts_generate` som kaller ElevenLabs text-to-speech API,
lagrer MP3-lyd i CAS, og oppretter media-node med has_media-edge.

Voice-preferanse løses i tre lag: eksplisitt i payload → nodens
metadata.voice_preference → ELEVENLABS_DEFAULT_VOICE env.
Dette er "mottaker-preferanse i metadata" — en node kan sette
voice_preference i sin metadata for å styre hvilken stemme som brukes.

Ny migrasjon 009: resource_usage_log-tabell for sporing av
ressursforbruk (TTS, AI, Whisper, CAS). Ref: docs/features/ressursforbruk.md

Endringer:
- maskinrommet/src/tts.rs: TTS-handler med ElevenLabs-integrasjon
- maskinrommet/src/intentions.rs: POST /intentions/generate_tts
- maskinrommet/src/jobs.rs: Dispatcher for tts_generate
- migrations/009_resource_usage_and_tts.sql: resource_usage_log
- scripts/maskinrommet-env.sh: ELEVENLABS_* env-variabler

Krever: ELEVENLABS_API_KEY i /srv/synops/.env (placeholder lagt til)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 23:40:46 +00:00
1803619622 Starter oppgave 10.4 2026-03-17 23:32:29 +00:00
3c1d85026b AI-oppsummering av kommunikasjonsnoder (oppgave 10.3)
Ny jobbtype `summarize_communication` som henter alle meldinger fra
en kommunikasjonsnode, sender dem til LiteLLM for oppsummering, og
oppretter en content-node med sammendraget. Sammendraget knyttes til
kommunikasjonsnoden med `belongs_to`-edge (del av samtalen) og
`summary`-edge (lett å finne sammendrag for en gitt samtale).

API-endepunkt: POST /intentions/summarize { communication_id }
Verifiserer at brukeren er deltaker i samtalen. Jobbprioritiet 3
(bakgrunn). Modell konfigurerbar via AI_SUMMARY_MODEL env-variabel.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 23:31:16 +00:00
b0c037ef71 Starter oppgave 10.3 2026-03-17 23:25:54 +00:00
63f022d739 AI-foreslåtte edges: LLM-analyse av innhold → topics og mentions (oppgave 10.2)
Ny jobbtype `suggest_edges` som automatisk trigges ved opprettelse av
content-noder med tilstrekkelig tekst (≥20 tegn). Sender innholdet til
LiteLLM (sidelinja/rutine) via AI Gateway, parser JSON-respons med
topics og mentions, og oppretter topic-noder + mentions-edges i grafen.

Flyten:
1. create_node oppdager content-node med nok tekst → enqueue suggest_edges
2. Worker henter node-innhold og eksisterende topics fra PG
3. LLM analyserer tekst og returnerer foreslåtte topics/mentions
4. Nye topic-noder opprettes (med ai_generated-flagg i metadata)
5. mentions-edges opprettes fra innholdsnode til topic/entitet-noder
6. Deduplisering: gjenbruker eksisterende topics ved case-insensitivt match

Filer:
- maskinrommet/src/ai_edges.rs: Ny modul med LLM-kall og edge-opprettelse
- maskinrommet/src/jobs.rs: suggest_edges registrert i dispatcher
- maskinrommet/src/intentions.rs: Trigger i create_node
- docs/: Oppdatert jobbkø og AI gateway-docs med ny jobbtype

NB: Krever gyldig API-nøkkel i LiteLLM (OpenRouter/Gemini/Anthropic).
Jobben feiler gracefully med retry+backoff ved manglende nøkkel.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 23:24:29 +00:00
b498700399 Starter oppgave 10.2 2026-03-17 23:14:11 +00:00
01ad35557f LiteLLM AI Gateway: Docker, DB-ruting, config-generering (oppgave 10.1)
Setter opp AI Gateway med LiteLLM som sentralisert proxy for alle
AI-kall. PG eier all modellkonfigurasjon — LiteLLM er stateløs.

- Migrasjon 008: ai_model_aliases, ai_model_providers, ai_job_routing
  med seed-data for sidelinja/rutine og sidelinja/resonering
- Config-generering fra PG: scripts/generate-litellm-config.sh
  filtrerer bort providers med tomme API-nøkler
- Docker-container kjører på sidelinja-net (intern, ingen eksponert port)
- Maskinrommet har AI_GATEWAY_URL via maskinrommet-env.sh
- API-nøkkel-placeholders i .env (GEMINI, ANTHROPIC, XAI)
- Oppdatert docs/infra/ai_gateway.md med faktisk config

Verifisert: container healthy, modellaliaser eksponert, maskinrommet
har korrekt gateway-URL. Reelle API-kall krever at Vegard fyller
inn leverandør-nøkler i /srv/synops/.env.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 23:12:46 +00:00
9d65e91244 Starter oppgave 10.1 2026-03-17 23:03:14 +00:00
7f5d23e0c6 Kunnskapsgraf: topic-noder, mentions-edges, visuell graf-visning (oppgave 9.4)
Backend:
- query_graph endpoint med rekursiv CTE-traversering (fokus + dybde)
- Filtrering på node_kind og edge_type, RLS-beskyttet
- Maks 200 noder, 1-3 hops dybde

Frontend:
- D3 force-directed graf på /graph med fargekodede noder
- Opprett topic-noder (node_kind='topic') direkte fra graf-visningen
- Opprett mentions-edges mellom vilkårlige noder
- Klikk for detaljer, dobbeltklikk for fokus, dra for å flytte
- Filter-legende for nodetyper og kanttyper
- Zoom, auto-fit, sidepanel med koblingsinfo
- Graf-knapp lagt til i mottaksflaten

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 23:01:57 +00:00
cce1c73b9e Starter oppgave 9.4 2026-03-17 22:56:09 +00:00
517311ca0f Frigjør stale oppgaver 9.4 og 10.1 etter API-feil 2026-03-17 22:55:47 +00:00
02fc38eb3d LiveKit oppsett: Docker-container for WebRTC (oppgave 11.1)
Legger til LiveKit som Docker-tjeneste for WebRTC-støtte.
Konfigurasjonen bruker livekit/livekit-server med signaling
proxyet gjennom Caddy på /livekit/*, og UDP 50000-50100 eksponert
direkte for WebRTC media-strømmer.

Endringer:
- docker-compose.yml: livekit-service (på /srv/synops/)
- livekit.yaml: server-konfig (på /srv/synops/config/livekit/)
- Caddy: /livekit/* route aktivert
- UFW: åpnet UDP 50000-50100 + TCP 7881
- maskinrommet-env.sh: LIVEKIT_URL/KEY/SECRET for Rust-API
- produksjon.md: oppdatert med LiveKit-detaljer

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 22:54:50 +00:00
82b733b865 Starter oppgave 11.1 2026-03-17 22:50:11 +00:00
f303f8a9b4 Starter oppgave 10.1 2026-03-17 22:43:57 +00:00
5d25024719 Starter oppgave 9.4 2026-03-17 22:35:38 +00:00
2ba830c7b4 Dagbok-visning: privat journal sortert på tid (oppgave 9.3)
Ny /diary-route som viser brukerens private noder — de som kun har
owner-edge og ingen delte edges til andre. Gruppert etter dato,
nyeste først, med inline oppretting av nye innlegg.

Dagbok-knapp med tellebadge lagt til i mottak-siden.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 22:34:13 +00:00
c505c9ebfc Starter oppgave 9.3 2026-03-17 22:28:49 +00:00
ea0671933d Kalender-visning: månedsrutenett med scheduled-edges (oppgave 9.2)
Ny rute /calendar som viser alle noder med scheduled-edge i et
månedsbasert kalenderrutenett. Bruker edge-metadata { at: ISO8601 }
for tidspunkt, med T12:00:00-konvensjon for heldagshendelser.

Funksjoner:
- Månedsnavigering med «I dag»-snarvei
- Drag-and-drop for å flytte hendelser mellom datoer (updateEdge)
- Inline-oppretting med tittel og valgfritt klokkeslett
- Fargekoding etter node_kind
- Hendelsesliste under rutenett for gjeldende måned
- Kalender-lenke med hendelsesteller på mottak-siden
- Sanntid via SpacetimeDB (edgeStore.byType('scheduled'))

Arkitekturvalg: Bruker scheduled-edges direkte fra SpacetimeDB
i stedet for legacy calendar_events-tabellen. En node blir en
kalenderoppføring ved å ha en scheduled-edge — konsistent med
«hva edges gjør med noder»-prinsippet.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 22:27:28 +00:00
f0a590303a Starter oppgave 9.2 2026-03-17 22:20:54 +00:00
d8c0cceb89 Kanban-visning: board med drag-and-drop statusendring (oppgave 9.1)
Implementerer kanban som noder+edges uten separate tabeller:
- Board = collection-node med metadata.board og metadata.columns
- Kort = content-noder med belongs_to-edge til board
- Status via status-edge (kort→board) med metadata.value
- Posisjon via belongs_to-edge metadata.position

Backend:
- POST /intentions/update_edge — oppdater edge-type/metadata
- GET /query/board?board_id= — hent kort med status og posisjon

Frontend:
- /board/[id] route med kolonner, drag-and-drop, kortoppretting
- Sanntid via SpacetimeDB edge-subscriptions
- Board-oppretting og navigasjon fra mottak-siden

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 22:13:22 +00:00
26e730e758 Starter oppgave 9.1 2026-03-17 22:05:19 +00:00
f81c8a96e0 Fullfør oppgave 8.2: Kontekstbasert identitet med alias
Når en bruker oppretter en node i en kommunikasjonskontekst der
brukerens alias er deltaker (owner/member_of/host_of), settes
created_by til alias-noden i stedet for brukerens hovednode.

Endringer:
- resolve_context_identity(): slår opp brukerens alias i konteksten
- create_node(): bruker alias som created_by når context_id er satt
- user_can_modify_node/edge(): gjenkjenner alias-eierskap ved endring/sletting
- 006_alias_aware_rls.sql: RLS-policies inkluderer alias-opprettede noder
- current_node_alias_ids(): PG-funksjon for alias-oppslag i RLS

Verifisert med integrasjonstest på server:
- Node i alias-kontekst → created_by = alias ✓
- Node uten kontekst → created_by = bruker ✓
- Update/delete av alias-node fungerer for hovedbruker ✓

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 19:19:36 +01:00
e7bb6d1f8a Starter oppgave 8.2 2026-03-17 19:10:43 +01:00
fab41419ec Fullfør oppgave 8.1: Alias-noder med system-edge
Implementert:
- POST /intentions/create_alias: oppretter person-node og alias-edge
  (system=true) fra brukerens hovednode til aliasnoden
- GET /query/aliases: returnerer brukerens alias-noder via alias-edges
- Alias-edgen er usynlig for traversering via eksisterende RLS-policy
  som filtrerer system-edges (edges.system = true)

Verifisert med integrasjonstest på server: opprettelse, PG-persistering,
spørring, og at system-edge er korrekt flagget.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 19:07:24 +01:00
b687c717c6 Starter oppgave 8.1 2026-03-17 18:54:51 +01:00
35701aeb2a Fullfør oppgave 7.8: SRT-eksport fra transkripsjons-segmenter
Nytt GET /query/segments/srt-endepunkt som genererer nedlastbar SRT-fil
fra transcription_segments-tabellen. Bruker RLS-verifisert tilgang.
Frontend har nedlastingsknapp i TranscriptionView med autentisert fetch.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 18:47:50 +01:00
f9cc1328cd Starter oppgave 7.8 2026-03-17 18:43:49 +01:00
b4ee80a97b Fullfør oppgave 7.7: Re-transkripsjonsflyt med side-om-side-sammenligning
Ny funksjonalitet for å kjøre re-transkripsjon på eksisterende media-noder
og sammenligne gammel vs ny versjon per segment. Manuelt redigerte segmenter
fra forrige versjon blir uthevet, og brukeren velger per segment hvilken
versjon som skal beholdes.

Backend (Rust):
- POST /intentions/retranscribe — trigger ny Whisper-jobb for media-node
- GET /query/transcription_versions — list alle versjoner for en node
- GET /query/segments_version — hent segmenter for spesifikk versjon
- POST /intentions/resolve_retranscription — anvend per-segment-valg

Frontend (Svelte):
- RetranscriptionCompare.svelte — side-om-side visning med per-segment-valg
- TranscriptionView: re-transkriber-knapp, auto-detect nye versjoner, polling
- API-klient: nye funksjoner for alle re-transkripsjonsendepunkter

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 18:41:09 +01:00
7233259d9d Starter oppgave 7.7 2026-03-17 18:32:49 +01:00
9e640adedb Fullfør oppgave 7.6: Transkripsjonsvisning med segmenter
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 18:32:08 +01:00
148e5c222c Starter oppgave 7.6 2026-03-17 18:21:54 +01:00