Commit graph

252 commits

Author SHA1 Message Date
7224cf9897 Starter oppgave 18.2 2026-03-18 06:14:26 +00:00
cad3f4b699 Fullfører oppgave 18.1: AI-preset node-type
Implementerer node_kind 'ai_preset' med metadata-validering og
8 standardprompter som seed-data.

Validering i maskinrommet (create_node + update_node):
- prompt (påkrevd, ikke-tom streng)
- model_profile (flash | standard)
- category (standard | custom)
- default_direction (tool_to_node | node_to_tool | both)
- icon (påkrevd, ikke-tom streng)
- color (påkrevd, hex-farge #RRGGBB)

Seed-presets: Rens tekst, Korrektør, Oppsummering, Oversett,
Skriv om for publisering, Trekk ut fakta, Forenkle, Endre tone.

8 enhetstester for valideringsfunksjonen.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 06:13:09 +00:00
dae06eecc6 Starter oppgave 18.1 2026-03-18 06:05:11 +00:00
a3cdfa9dc2 Fullfører oppgave 17.7: FFmpeg feilmeldinger til bruker
Tre endringer som sammen gir brukeren innsyn i FFmpeg-feil:

1. Backend: Nytt GET /query/job_status-endepunkt i queries.rs.
   Frontenden pollet allerede denne URLen, men endepunktet manglet.
   Returnerer status, result og error_msg fra job_queue.

2. RenderDialog: Ny error-tilstand med formatFfmpegError() som
   trekker ut lesbar feilmelding fra FFmpeg stderr-dump. Viser
   kort oppsummering + ekspanderbar full feilmelding via <details>.

3. Studio-side: Sender renderError til RenderDialog som errorMessage.
   Toast-varselet vises kun når dialogen er lukket (unngår duplisering).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 06:03:59 +00:00
9f4dfee232 Starter oppgave 17.7 2026-03-18 05:58:19 +00:00
ce56e31de2 Fullfører oppgave 17.6: Periodisk CAS tmp-opprydding
Legger til cleanup_tmp() i CasStore som sletter orphaned .tmp-filer
eldre enn 1 time. Disse oppstår når en skriveprosess krasjer midt i
en atomisk CAS-skriveoperasjon (skriv til tmp, rename til endelig path).

Ny bakgrunnsloop start_tmp_cleanup_loop() kjører hver time og fjerner
foreldede temp-filer. Følger samme mønster som pruning- og
disk-monitor-loopene.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 05:57:10 +00:00
a3f8ca2dfa Starter oppgave 17.6 2026-03-18 05:54:47 +00:00
6f50f66431 Fullfører oppgave 17.5: Job-polling opprydding i lydstudioet
Tre forbedringer i studio-siden:

1. Interval/timeout-opprydding ved navigering: Polling-interval og
   timeout lagres i komponent-variabler og ryddes opp via $effect
   cleanup når komponenten demonteres. Forhindrer memory leaks og
   ghost-requests etter navigering bort fra studio-siden.

2. Feilmelding etter N mislykkede polling-forsøk: Etter 5 feilede
   statussjekker (nettverksfeil eller HTTP-feil) vises en
   feilmelding til brukeren i stedet for stille ignorering.
   Timeout-feil og jobb-feil vises også i UI.

3. Metadata JSON.parse i try/catch: Hindrer at ugyldig metadata-JSON
   krasjer hele studio-siden. Logger feilen og returnerer null.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 05:53:28 +00:00
24d752024c Starter oppgave 17.5 2026-03-18 05:50:51 +00:00
6e04f3c013 Fullfører oppgave 17.4: Frontend input-begrensninger
Legger til min/max-attributter på alle tallfelter i OperationPanel
slik at nettleseren hindrer ugyldig input før det sendes til backend.
Grensene matcher backend-valideringen i audio.rs:

- silenceThreshold: -96 til 0 dB
- silenceMinMs: 1 til 60000 ms
- normTarget: -70 til 0 LUFS
- fadeInMs/fadeOutMs: 1 til 300000 ms
- compThreshold: -60 til 0 dB
- compRatio: 1 til 20 (max lagt til, min fantes allerede)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 05:49:42 +00:00
b20642a433 Starter oppgave 17.4 2026-03-18 05:48:17 +00:00
1d1a316a1c Fullfører oppgave 17.3: Fade/silence-logikk
Tre fikser i audio.rs:

1. Fade-out start clampes til 0 i stedet for å hoppes over
   når effective_duration < fade_duration. Tidligere ble faden
   stille droppet — nå starter den alltid fra minst 0.

2. Adaptiv silence-margin: margin (200ms) begrenses til maks
   halve regionens varighet. Korte stillhetsregioner (<400ms)
   fikk tidligere hele marginen spist opp og ble aldri kuttet.

3. Ny validate_fade_durations() gir feilmelding når fade-varighet
   overstiger lydens totale varighet. Kalles fra process_audio
   etter at vi kjenner faktisk varighet.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 05:47:07 +00:00
4ea0229935 Starter oppgave 17.3 2026-03-18 05:44:41 +00:00
7eccbd0dc3 Fullfører oppgave 17.2: FFmpeg-parametervalidering
Legger til validate_operations() som sjekker alle numeriske verdier
i EDL-operasjoner før de interpoleres i FFmpeg-filterstrenger.
Dette forhindrer ugyldige/farlige verdier fra å nå ffmpeg subprocess.

Validerte parametere:
- Cut: start/end ikke-negativ, end > start
- Normalize: target_lufs mellom -70 og 0
- TrimSilence: threshold_db -96..0, min_duration 1..60000ms
- FadeIn/Out: duration 1..300000ms
- NoiseReduction: strength_db -80..0
- Equalizer: gain -30..+30 dB per bånd
- Compressor: threshold -60..0 dB, ratio 1..20

Validering kjøres ved inngang til process_audio() og detect_silence().
NaN/Inf-verdier avvises eksplisitt. Alle feil samles og returneres samlet.

12 enhetstester verifiserer grenseverdier og feiltilfeller.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 05:43:31 +00:00
a4e2a112d5 Starter oppgave 17.2 2026-03-18 05:40:43 +00:00
9f66f114ff Fullfører oppgave 17.1: Responsivt studio-layout
Lydstudioet var kun desktop-optimalisert med fast sidebar (w-72).
Nå responsivt med to moduser:

- Desktop (lg+): Sidebar med verktøypanel til høyre, som før
- Mobil/tablet (< lg): Waveform fyller full bredde, verktøypanel
  tilgjengelig via flytende knapp som åpner bottom sheet (modal).
  Operation-badge på knappen viser antall aktive operasjoner.

Header: Kompaktere padding på mobil, tittel truncates, audio-info
skjules på små skjermer. min-w-0 på hovedområdet hindrer overflow.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 05:39:19 +00:00
4edad399cd Starter oppgave 17.1 2026-03-18 05:36:17 +00:00
ac8f8c508d Fullfører oppgave 16.7: Stemmeeffekter med robot og monster voice
Robotstemme: Ring-modulasjon via OscillatorNode som modulerer
GainNode.gain — gir metallisk, Dalek-aktig effekt. Justerbar
frekvens (30–300 Hz) og modulasjonsdybde (0–100%).

Monsterstemme: Egenutviklet AudioWorkletProcessor med phase vocoder
for sanntids pitch-shifting. Bruker overlap-add med 2048-sample FFT
og 4x overlap for ~42ms latens ved 48kHz. Pitch-faktor 0.5x–2.0x.

UI: Effektvelger-knapper (Robot/Monster) i FX-seksjon per kanal,
med fargekodede parametersliders som vises når effekten er aktiv.
On/off-state synkroniseres via STDB toggle_effect, parametere er
per-klient (ulike brukere kan ha forskjellige monitorinnstillinger).

STDB: Lagt til set_effect_param reducer for fremtidig param-synk
(krever spacetime CLI for publish — ikke deployet ennå).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 05:34:59 +00:00
6732a15e4d Starter oppgave 16.7 2026-03-18 05:25:46 +00:00
171c9b991a Fullfører oppgave 16.6: EQ-effektkjede med per-kanal toggles og presets
Implementerer tre EQ-effekter i Web Audio-grafen:
- Fat bottom: BiquadFilterNode lowshelf +8dB @ 200Hz
- Sparkle: BiquadFilterNode highshelf +4dB @ 10kHz
- Exciter: WaveShaperNode (soft-clip saturation) + highshelf +4dB @ 3.5kHz
- Highpass 80Hz alltid aktiv for rumble-fjerning

Signalkjede per kanal:
  Source → Analyser → HighPass → FatBottom → Exciter → Sparkle → Gain → Master

Per-kanal toggles synkroniseres via STDB toggle_effect reducer
(allerede implementert i fase B). UI viser fargede toggle-knapper
og preset-velger (Av, Podcast-stemme, Radio-stemme).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 05:24:24 +00:00
28a600dd9e Starter oppgave 16.6 2026-03-18 05:18:54 +00:00
8acb5a8731 Fullfører oppgave 16.5: Sound pads med 4×2 pad-grid
Implementerer lydpads (inspirert av RødeCaster Pro II) i mixeren:

- mixer.ts: Nytt pad-system med AudioBuffer-caching, GainNode per pad,
  og one-shot AudioBufferSourceNode-avspilling. Funksjoner for load,
  play, stop, og gain-kontroll.

- livekit.ts: Data message-støtte (sendDataMessage, onDataMessage) for
  synkronisert pad-avspilling på tvers av LiveKit-deltakere. Bruker
  reliable delivery med topic-filtrering.

- SoundPadGrid.svelte: 4×2 responsivt pad-grid med fargede knapper.
  Forhåndslaster lydfiler fra CAS til AudioBuffer. Visuell feedback
  ved avspilling (scale-animasjon). Konfigurasjonsmodus for å sette
  label, farge og laste opp lydfil per pad. Pad-konfig lagres i
  metadata.mixer.pads på samlingsnoden.

- MixerTrait.svelte: Integrerer SoundPadGrid mellom kanalstriper og
  master-seksjon. Sender isViewer-prop for tilgangskontroll.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 05:17:30 +00:00
00d4df6f53 Starter oppgave 16.5 2026-03-18 05:09:36 +00:00
453ec4fb59 Fullfører oppgave 16.4: Delt mixer-kontroll via SpacetimeDB
Implementerer sanntids mixer-synkronisering mellom alle deltakere i et
LiveKit-rom via SpacetimeDB. Når én deltaker justerer gain eller muter
en kanal, oppdateres alle klienters Web Audio-graf og UI umiddelbart.

SpacetimeDB-modul (Rust):
- MixerChannel-tabell med room_id/target_user_id-indekser
- Reducers: set_gain (clamped 0.0-1.5), set_mute, toggle_effect,
  create/delete_mixer_channel, set_mixer_role (editor/viewer)
- Viewer-sjekk i reducers — viewer kan ikke endre andres kanaler
- Opprydding av mixer-kanaler i close_live_room og clear_all

Frontend (SvelteKit):
- mixerChannelStore med reaktive callbacks og room_id-indeks
- MixerTrait leser delt state fra STDB, skriver endringer via reducers
- suppressRemoteSync-flagg forhindrer feedback-loop ved egne endringer
- Viewer-modus: disabled sliders/knapper for låste deltakere
- Visuell (V)-indikator for viewer-kanaler

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 05:08:23 +00:00
de498f26a1 Starter oppgave 16.4 2026-03-18 04:58:37 +00:00
f1c9b281bc Fullfører oppgave 16.3: MixerTrait-komponent med kanalstriper
Implementerer Mixer-UI som trait-komponent for samlingssider:
- Kanalstripe per deltaker med volumslider (0–150%), VU-meter (CSS),
  nød-mute-knapp (rød, tydelig) og navnelabel
- Master-seksjon med fader, VU-meter og master-mute
- Responsivt design: stacked layout på mobil, horisontal på desktop
- Animert VU-meter via requestAnimationFrame og AnalyserNode-data
- Integrert i collection-siden som 'mixer'-trait

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 04:57:22 +00:00
82c45c4208 Starter oppgave 16.3 2026-03-18 04:54:25 +00:00
9fb1dcf93b Fullfører oppgave 16.2: Web Audio mixer-graf
Oppretter dedikert mixer-modul (mixer.ts) som eier hele Web Audio-grafen:
- AudioContext med master GainNode og master AnalyserNode
- Per-kanal signalkjede: MediaStreamSource → AnalyserNode → GainNode → MasterGain → destination
- AnalyserNode per kanal gir peak/RMS-nivådata for VU-meter
- API for gain-kontroll (per-kanal og master), mute/unmute, nivåavlesning
- livekit.ts delegerer all lydrutning til mixer.ts

Arkitekturen er klar for fremtidige faser: effektkjeder kan settes inn
mellom source og gain, sound pads kan legge til kanaler, og SpacetimeDB
kan synkronisere mixer-state.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 04:53:13 +00:00
4630820876 Starter oppgave 16.2 2026-03-18 04:50:19 +00:00
a838e0e1c2 Fullfører oppgave 16.1: LiveKit-klient i frontend
Installerer livekit-client og bygger grunnlaget for sanntidslyd:
- $lib/livekit.ts: LiveKit-wrapper med romtilkobling, deltakersporing,
  og Web Audio-ruting. Auto-attach av <audio> er deaktivert — all lyd
  rutes gjennom AudioContext med GainNode per deltaker (klar for mixer).
- api.ts: joinCommunication/leaveCommunication API-funksjoner
- RecordingTrait.svelte: UI for tilkobling, mikrofon-toggle og deltakerliste
  med live speaking-indikator

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 04:49:02 +00:00
7555038106 Starter oppgave 16.1 2026-03-18 04:44:03 +00:00
5771d1eed6 Fullfører oppgave 15.9: Brukersynlig forbruk
Legger til to nye API-endepunkter for brukersynlig ressursforbruk:
- GET /my/usage — brukerens eget forbruk (filtrert på triggered_by)
- GET /query/node_usage — forbruk for én node (kun eier/admin)

Frontend:
- /profile — profilside med grafstatistikk og forbruksoversikt
- NodeUsage-komponent integrert i samlings-detaljsiden
- Brukernavn i header lenker nå til profilsiden

Tilgangssjekk: nodeforbruk krever created_by eller owner/admin-edge.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 04:42:47 +00:00
49b9afb64e Starter oppgave 15.9 2026-03-18 04:35:28 +00:00
d7dffa06e6 Fullfører oppgave 15.8: Forbruksoversikt i admin
Aggregert ressursforbruk-dashboard som spør mot resource_usage_log
(oppgave 15.7). Tre visninger: totaler per ressurstype, per samling,
og daglig tidsserie. AI drill-down viser forbruk per jobbtype og
modellnivå (fast/smart/deep).

Backend: GET /admin/usage med days- og collection_id-filtre.
Frontend: /admin/usage med filterbare tabeller og fargekodede kort.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 04:34:08 +00:00
973ccece35 Starter oppgave 15.8 2026-03-18 04:26:15 +00:00
eb81055ef4 Fullfører oppgave 15.7: Ressursforbruk-logging
Sentralisert logging av alle ressurskrevende operasjoner til
resource_usage_log-tabellen (opprettet i migrasjon 009).

Ny kode:
- resource_usage.rs: hjelpemodul med log() og find_collection_for_node()
- bandwidth.rs: Caddy JSON-logg-parser med nattlig batch-jobb (kl 03:00)

Logging lagt til i handlere:
- AI: summarize, ai_edges (token-telling via LiteLLM usage-felt),
  agent (placeholder — claude CLI gir ikke token-info)
- Whisper: duration_seconds, model, language, mode
- TTS: refaktorert til sentralisert modul, lagt til collection_id
- CAS: logger nye filer ved upload (ikke dedup)
- LiveKit: logger join-hendelser (faktisk deltaker-minutter
  krever webhook-integrasjon i fremtiden)

Caddy-config: JSON access logging aktivert for sidelinja.org og
synops.no i /srv/synops/config/caddy/Caddyfile (utenfor repo).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 04:24:54 +00:00
9a5d24c850 Starter oppgave 15.7 2026-03-18 04:14:15 +00:00
56b7df8bf8 Fullfører oppgave 15.6: Serverhelse-dashboard
Nytt admin-dashboard for sanntids serverhelse med fire hoveddeler:

1. Tjeneste-status: Parallelle helsesjekker for alle 7 tjenester
   (PG, STDB, Caddy, Authentik, LiteLLM, Whisper, LiveKit) med
   latens-måling og statusrapportering (up/down/degraded).

2. System-metrikker: CPU-load via /proc/loadavg, minne via
   /proc/meminfo, disk via statvfs, oppetid via /proc/uptime.
   Vises med progress-bars og fargekodede terskler.

3. PG-statistikk: Aktive tilkoblinger, maks-tilkoblinger,
   databasestørrelse og aktive spørringer.

4. Logg-tilgang: Filtrerbar visning av logger fra alle tjenester.
   Bruker journalctl for systemd-tjenester og docker logs for
   containere. Konfigurerbart antall linjer per tjeneste.

Backend: health.rs med tokio::join! for parallelle sjekker.
Frontend: /admin/health med auto-polling hvert 10. sekund.
Backup-sjekk rapporterer ok/stale/missing (ingen backup satt opp ennå).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 04:12:54 +00:00
9d4902ec48 Starter oppgave 15.6 2026-03-18 04:03:32 +00:00
b64f217637 Fullfører oppgave 15.5: Ressursstyring for jobbkø
Implementerer prioritetsregler, ressursgrenser og LiveKit-bevisst
resource governor for jobbkø-workeren, pluss disk-overvåking med varsling.

Hovedkomponenter:

1. Prioritetsregler (job_priority_rules-tabell):
   - Konfigurerbar base_priority, cpu_weight, max_concurrent per jobbtype
   - LiveKit-justering: livekit_priority_adj og block_during_livekit
   - Timeout per jobbtype
   - Admin-API for å endre regler uten restart

2. Ressurs-governor i worker-loopen:
   - Semaphore: maks 3 samtidige jobber
   - CPU-vektgrense: total vekt maks 8 (Whisper=5, render=1, etc.)
   - Per-type concurrency-grense
   - LiveKit-status sjekkes med 10s cache-TTL
   - Jobber utsettes/nedprioriteres ved aktive LiveKit-rom
   - Individuell timeout per jobb (default 600s)
   - Jobber kjøres i egne tokio-tasks (parallell dispatch)

3. Disk-overvåking:
   - Sjekker diskbruk hvert 60. sekund via statvfs
   - Terskler: 85% warning, 90% critical, 95% emergency
   - Logger til disk_status_log (siste 1000 målinger beholdes)
   - Admin-API: GET /admin/resources/disk med historikk

4. Admin-API:
   - GET /admin/resources — samlet ressursstatus
   - GET /admin/resources/disk — diskstatus med historikk
   - POST /admin/resources/update_rule — oppdater prioritetsregel

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 04:02:11 +00:00
d57f3d1324 Starter oppgave 15.5 2026-03-18 03:53:01 +00:00
3fbe42f207 Fullfører oppgave 15.4: AI Gateway admin-UI
Implementerer /admin/ai med fire faner:
- Modeller & fallback: oversikt over aliaser med fallback-kjeder,
  toggle aktiv/inaktiv, legg til/fjern modeller, endre prioritet
- Ruting: jobbtype → modellalias mapping med dropdown-endring
- Forbruk: aggregert tokenforbruk per samling/alias/jobbtype
- API-nøkler: viser hvilke env-variabler som er satt (aldri verdier)

Backend (maskinrommet/src/ai_admin.rs):
- GET /admin/ai — full oversikt med aliaser, providers, ruting, forbruk
- GET /admin/ai/usage — forbruk med filtre (dager, samling)
- POST-endepunkter for CRUD på aliaser, providers og ruting-regler

PG er single source of truth. API-nøkler lagres kun som env-variabler,
admin-panelet viser bare om de er satt eller mangler.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 03:51:45 +00:00
6accce99e6 Starter oppgave 15.4 2026-03-18 03:42:23 +00:00
07fa10620b Fullfører oppgave 15.3: Jobbkø-oversikt med admin-UI
Admin kan nå se, filtrere, retrye og avbryte jobber via /admin/jobs.

Backend:
- jobs.rs: list_jobs(), count_by_status(), distinct_job_types(),
  retry_job(), cancel_job() for admin-spørringer
- intentions.rs: GET /admin/jobs, POST retry_job/cancel_job handlers
- main.rs: tre nye ruter

Frontend:
- /admin/jobs: statusoppsummering med antall per status, filter på
  type/status, paginert tabell med retry/avbryt-knapper, 5s polling
- /admin: navigasjonslenke til jobbkø

Migrasjon:
- 013_job_queue.sql: formaliserer job_queue-tabellen med admin-indekser

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 03:40:56 +00:00
8242dd8360 Starter oppgave 15.3 2026-03-18 03:32:52 +00:00
d8e44fe57e Fullfører oppgave 15.2: Graceful shutdown med vedlikeholdsmodus
Implementerer koordinert nedstenging der admin setter et
vedlikeholdstidspunkt, brukere ser nedtelling, og systemet
stenger ned trygt etter at aktive jobber er ferdige.

Nye filer:
- maskinrommet/src/maintenance.rs — MaintenanceState med atomiske
  flagg, shutdown-koordinator (vent på scheduled_at → blokker
  nye jobber/LiveKit → vent på kjørende jobber → exit)
- frontend/src/routes/admin/+page.svelte — admin-panel for
  vedlikehold med statusvisning og aktive sesjoner

Endringer:
- jobs.rs: sjekker maintenance.is_active() før dequeue
- intentions.rs: nye endepunkter (initiate/cancel/status), blokkerer
  join_communication under vedlikehold
- main.rs: MaintenanceState i AppState, nye ruter
- api.ts: klientfunksjoner for maintenance-API
- adminpanelet.md: dokumenterer implementerte endepunkter

Flyt: admin → GET /admin/maintenance_status (se aktive sesjoner)
→ POST /intentions/initiate_maintenance → varsel broadcast via STDB
→ frontend nedtelling → scheduled_at nådd → active=true → jobbkø
pauset + LiveKit blokkert → vent maks 5 min → process::exit(0)
→ systemd restarter maskinrommet.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 03:31:32 +00:00
9d405f90e3 Starter oppgave 15.2 2026-03-18 03:22:34 +00:00
831666012a Fullfører oppgave 15.1: Systemvarsler via STDB
Implementerer systemvarsler (system_announcement-noder) som vises som
banner/toast for alle aktive klienter i sanntid via SpacetimeDB.

Backend (maskinrommet):
- POST /intentions/create_announcement: oppretter varslingsnode med
  validering av announcement_type (info/warning/critical), datoer
  (scheduled_at, expires_at) og blocks_new_sessions. Visibility
  settes automatisk til 'open' for broadcast til alle klienter.
- POST /intentions/expire_announcement: fjerner varslingsnode med
  tilgangskontroll (kun eier kan slette).

Frontend:
- SystemAnnouncements.svelte: filtrerer nodeStore for aktive
  system_announcement-noder, skjuler utløpte (expires_at), viser
  nedtelling for planlagte hendelser (scheduled_at). Fargekoding:
  rød (critical), gul (warning), blå (info). Critical kan ikke
  dismisses. Oppdaterer nedtelling hvert sekund.
- Komponent lagt til i +layout.svelte for global synlighet.
- API-funksjoner (createAnnouncement, expireAnnouncement) i api.ts.

Ref: docs/concepts/adminpanelet.md § "Systemvarsler og vedlikeholdsmodus"

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 03:21:18 +00:00
1c313f3857 Starter oppgave 15.1 2026-03-18 03:15:06 +00:00
1425a82cdd Fullfører oppgave 14.17: A/B-testing for presentasjonselementer
Implementerer automatisk A/B-testing for forside-varianter:

- PG-migrasjon 012: ab_events-tabell for impression/klikk-logging
  med hour_of_week (0-167) for tidspunkt-normalisering
- Variant-rotasjon: ab_select() velger tilfeldig blant testing-varianter
  ved forside-rendering, winner prioriteres, retired filtreres bort
- Impression-logging: asynkron fire-and-forget ved forside-serve
  (både cache-hit og -miss), lagres i ab_events
- Klikk-attribusjon: artikkelbesøk sjekker forside-cache for aktive
  AB-varianter og logger klikk. Eksplisitt tracking via
  GET /pub/{slug}/t/{article_id}?v={edge_id}
- Periodisk evaluator (300s intervall): z-test for proporsjoner
  (p < 0.05), minimum 100 impressions per variant, oppdaterer
  edge-metadata (ab_status, impressions, clicks, ctr)
- Redaktør-overstyring: POST /intentions/ab_override markerer
  valgt variant som winner, andre som retired (krever owner/admin)
- Auto-initialisering: maybe_start_ab_test() setter ab_status=testing
  automatisk når >1 variant av samme type opprettes

Alle 42 tester passerer inkludert 3 nye z-test-tester.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 03:13:39 +00:00