Commit graph

521 commits

Author SHA1 Message Date
c46c9e364e / viser arbeidsflaten direkte, ikke mottak
Flyttet workspace-innholdet til rotpagen. Mottak er utdatert —
alt bor i arbeidsflaten som paneler. Fjernet /workspace-rute
og tilbake-pilen i headeren.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 04:44:18 +00:00
70b33d5387 ws.synops.no: ren domene/ruting/auth-opprydding
Hvert subdomain har én jobb. Offentlig og internt er separate domener.
- synops.no = statisk landingsside + /pub/* + /media/*
- ws.synops.no = appen (SvelteKit + /api/* → maskinrommet)
- workspace.synops.no → redirect til ws.synops.no (legacy)
- Fjernet hostname-sjekker fra hooks.server.ts
- Fjernet LandingPage.svelte (landingsside er statisk HTML)
- Alle API-URLer peker til 127.0.0.1:3100 (ikke sidelinja.org)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 04:31:39 +00:00
7c4d51a267 workspace.synops.no / viser arbeidsflaten direkte, ikke redirect til /workspace
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 03:51:23 +00:00
be6aa2b1bc workspace.synops.no: redirect til login/workspace, aldri vis landingsside
Host-basert logikk i hooks: workspace.* sender uautentiserte til
login og autentiserte til /workspace. synops.no viser landingsside.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 03:47:02 +00:00
32c3dfc1b0 synops.no = offentlig, workspace.synops.no = app
Klar separasjon:
- synops.no: statisk landingsside + publisert innhold (/pub/*)
- workspace.synops.no: SvelteKit-app (arbeidsflate, chat, alt)
- Alle login-lenker peker til workspace.synops.no
- Cookie-domene tilbake til default (ikke .synops.no)
- Caddy: to separate site-blokker

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 03:40:21 +00:00
ed632f686d Cookie-domene .synops.no for subdomain-deling + workspace.synops.no
Auth-cookies settes på .synops.no slik at de deles mellom
synops.no, workspace.synops.no, og fremtidige subdomener.
Caddy proxyer workspace.synops.no til SvelteKit.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 03:35:10 +00:00
6411347aec Redirect innloggede fra / til /workspace
Uautentiserte ser landingssiden, innloggede sendes rett til
arbeidsflaten. Mottak er tilgjengelig som eget panel.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 03:33:29 +00:00
8d914ed38f Landingsside som Svelte-komponent, fjern iframe (X-Frame-Options blokkerte)
LandingPage.svelte med all CSS og HTML innbakt. Ingen iframe,
ingen ekstern fil-avhengighet. Fungerer i alle nettlesere.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 03:27:45 +00:00
daf9660353 Landingsside via iframe til /landing for uautentiserte
Unngår å duplisere landingssiden i SvelteKit — iframe loader
den statiske filen fra Caddy /landing-ruten.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 03:20:19 +00:00
7233488c71 Fiks signin-loop: / tillatt for uautentiserte, viser inline landingsside
hooks.server.ts: / unntatt fra auth-redirect
+page.svelte: viser landingsside for uautentiserte, mottak for innloggede
Ingen separat statisk fil — alt i SvelteKit

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 03:17:08 +00:00
27f7848a90 Migrer fra sidelinja-sentrisk til synops-sentrisk domener
- Caddy: api.synops.no og git.synops.no som primære
- Frontend .env: AUTHENTIK_ISSUER → auth.synops.no (lokal, gitignored)
- CLAUDE.md: oppdatert domeneliste og git-URL-er

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 03:13:59 +00:00
57a3874310 Fjern footer: AI-verktøy og ressursforbruk ut av hardkodet posisjon
Footer-feltet fjernet fra workspace og collection-sider.
AI-verktøy og ressursforbruk blir paneler i canvas (fremtidig).
Canvas får full høyde.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 03:04:29 +00:00
b57efa1dc6 Fiks fullskjerm: portal til document.body for å unngå Canvas transform
Canvas sin CSS transform bryter position:fixed (kjent CSS-gotcha).
Fullskjerm flytter nå DOM-elementet til body, og tilbake ved exit.
z-index 9999 og bakgrunnsfarge #0a0a0b.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 02:59:34 +00:00
df8b3b2f2e Fiks BlockShell-knapper: stopPropagation forhindrer drag-capture
Minimer, maksimer og lukk-knapper ble fanget av header-dragging
pga setPointerCapture. Nå: stopPropagation på controls-div og
individuelle knapper, pluss .closest('.blockshell-controls') sjekk.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 02:56:48 +00:00
25a543e957 Fikseliste: kontekst-velger med rename, ny flate, deling via edges
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 02:51:12 +00:00
3255c25c1e Fikseliste: workspace er appen, alle ruter blir deep links til paneler
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 02:40:47 +00:00
37c3bc8b8c Oppdater fikseliste: footer fjernes, innstillingsmeny i header, fargevelger
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 02:37:13 +00:00
b190c79fb3 Mørk canvas-bakgrunn + subtile indigo grid-linjer
Canvas var #f8f9fa (nesten hvit). Nå #0a0a0b.
Grid-linjer fra svart 5% til indigo 6% for synlighet på mørk bakgrunn.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 02:29:18 +00:00
543b0ca29f Mørkt tema på alle sider: workspace, canvas, blockshell, traits, collection
Erstattet alle hardkodede lyse farger (white, #f0f2f5, #f3f4f6)
med mørke (#0a0a0b, #1c1c20, #242428) i alle Svelte-komponenter.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 02:27:15 +00:00
ab30ab8c65 Fiks mørkt tema: force bakgrunn på html/body/min-h-screen, lysere surface
Bakgrunnen var fortsatt hvit fordi Tailwind-klasser overstyrte body.
Nå forcert med !important på html, body, .min-h-screen.
Surface justert fra #141416 til #1c1c20 for bedre kontrast på kort.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 02:19:12 +00:00
98a7b0ab62 Mørkt tema: matcher landingssiden (accent indigo, surface #141416)
Overstyr Tailwind grays med mørke fargevariabler. Inputs, borders,
shadows, scrollbar, knapper og tekst tilpasset. Samme palett som
synops.no landingssiden.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 02:16:32 +00:00
09f69d1fdb Fiks workspace JSONB type-mismatch: metadata leses som serde_json::Value
WorkspaceRow.metadata var String, men PG-kolonnen er JSONB.
Første opprettelse fungerte (inserter Value), men oppslag
feilet ved andre besøk.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 02:09:39 +00:00
6370b02cc7 26.7 ferdig: utgående varsler med brukerpreferanser
Vaktmesteren kan nå sende epost-varsler og WebSocket-push til brukere
via synops-notify, med respekt for brukerens preferanser.

Endringer:
- jobs.rs: send_notification jobbtype som delegerer til synops-notify CLI
- synops-notify: preferansesjekk fra metadata.preferences.notifications
  (opt-out-modell, per-kanal og per-type bryter, --skip-preferences)
- intentions.rs: POST /intentions/send_notification (admin-only)
- Dokumentasjon: docs/features/varsler.md

Preferanseskjema (i brukernodens metadata):
  preferences.notifications.email: bool (global epost-bryter)
  preferences.notifications.ws: bool (global WS-bryter)
  preferences.notifications.<type>: bool (per-type, f.eks. task_assigned)
2026-03-19 02:08:00 +00:00
7be810b994 Starter oppgave 26.7 2026-03-19 01:57:17 +00:00
259868574d 26.6 ferdig: domene-alias implementert i synops-mail 2026-03-19 01:57:08 +00:00
36bbe0a193 Fjern frase-sjekk, legg til domene-alias i synops-mail
- Fjernet "Kjære vaktmester"-krav: avsender-verifisering via
  auth_identities.email er tilstrekkelig spam-filter
- Domene-alias: mottaker-username oppslås i auth_identities
  uavhengig av domene. vegard@synops.no, vegard@sidelinja.org,
  vegard@vegard.info ruter til samme bruker

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 01:55:37 +00:00
25713c4482 26.5 ferdig: synops-mail --receive med avsender-verifisering og node-opprettelse
Implementert fullstendig epost-mottak pipeline:
- Parser raw RFC 5322 epost fra stdin via mailparse
- Sjekk 1: Envelope-sender matches auth_identities.email (case-insensitive)
- Sjekk 2: Body starter med konfigurerbar aktiveringsfrase (default: "Kjære vaktmester")
- Begge sjekker må bestå — ellers forkastes eposten stille (exit 0, ingen bounce)
- Ved match: oppretter content-node med visibility=hidden, created_by=bruker
- Metadata lagrer source=email, from, to, subject for sporbarhet
- UTF-8 håndtering: prøver raw bytes som UTF-8 først, faller tilbake til mailparse charset
- Aktiveringsfrase konfigurerbar via --phrase eller SYNOPS_MAIL_PHRASE env
2026-03-19 01:52:53 +00:00
ad8aa2181a Starter oppgave 26.5 2026-03-19 01:47:14 +00:00
a6740f82e3 26.4 ferdig: Postfix som receive-only MTA med pipe til synops-mail
Postfix installert og konfigurert som lokal MTA kun for epost-mottak.
Ingen relay, ingen utgående kø — utgående bruker msmtp/Brevo som før.

Konfigurasjon:
- virtual_mailbox_domains: synops.no, sidelinja.org, vegard.info
- Catch-all: alle adresser under domenene aksepteres
- virtual_transport → synops-pipe: pipe(8) leverer til synops-mail
- default_transport = error: blokkerer utgående SMTP
- synops-mail --receive stub: leser stdin, logger, exit 0

Verifisert: lokal SMTP-test viser at epost aksepteres, pipes til
synops-mail, og logges korrekt i /var/log/mail.log.
2026-03-19 01:32:04 +00:00
8e8c9ba1dd Starter oppgave 26.4 2026-03-19 01:26:15 +00:00
c7338a8389 26.3 ferdig: MX-records satt opp for alle domener 2026-03-19 01:26:05 +00:00
9403c6d7cf 26.3 delvis: port 25 åpnet, MX-instruksjoner dokumentert
Port 25/tcp åpnet i UFW for innkommende SMTP (forutsetning for
epost-mottak). Dokumentert nøyaktige MX/A/SPF-records som trengs
i docs/setup/produksjon.md.

Selve DNS-endringene må gjøres manuelt i Hetzner DNS Console
(dns.hetzner.com) — Claude har ikke browser eller API-token.
Detaljerte instruksjoner i tasks.md.
2026-03-19 00:57:40 +00:00
c07317f6d8 Starter oppgave 26.3 2026-03-19 00:55:17 +00:00
b83895c8fb 26.2 ferdig: Brevo SMTP-credentials konfigurert 2026-03-19 00:55:07 +00:00
c5239d2923 Feed-redirect: 301 for podcast som flyttes til ny host (oppgave 30.8)
Når redirect_feed er satt i podcast-trait, returnerer maskinrommet
HTTP 301 Moved Permanently med Location-header i stedet for å serve
feeden. iTunes new-feed-url-taggen bevares også i RSS-en for klienter
som ikke følger 301.

Admin-UI: erstatter det enkle tekstfeltet med tre tilstander:
- Inaktiv: knapp "Flytt podcast til annen plattform..."
- Bekreftelse: advarsel + URL-felt + rød "Aktiver redirect"-knapp
- Aktiv: gul statusindikator med deaktiver-knapp

Backend: sjekker redirect_feed tidlig i generate_feed() og returnerer
301 før noe annet arbeid gjøres (DB-oppslag for episodes osv).
2026-03-19 00:31:39 +00:00
ba8d361626 Starter oppgave 30.8 2026-03-19 00:25:31 +00:00
a469614ca1 Ferdigstill oppgave 30.7: podcast import wizard
- Inkluder samlinger med rss-trait (ikke bare podcast-trait) i dropdown
- Fiks slug-lesing fra traits.publishing.slug
- Installer synops-import-podcast til /usr/local/bin
- Marker oppgave 30.7 som ferdig i tasks.md
2026-03-19 00:21:25 +00:00
62b1ecd0b6 Podcast import wizard: backend + frontend (oppgave 30.7)
Backend (maskinrommet):
- Nytt modul podcast_import.rs med 4 endepunkter:
  POST /admin/podcast/import-preview (dry-run via CLI)
  POST /admin/podcast/import (starter jobb i køen)
  GET /admin/podcast/import-status (poll jobbstatus)
  GET /admin/podcast/collections (samlinger med podcast-trait)
- Ny jobbtype import_podcast i jobs.rs dispatcher

Frontend:
- Ny wizard-side /admin/podcast-import med 5 steg:
  1. RSS-URL + samling → forhåndsvisning
  2. Import (spinner med jobbstatus-polling)
  3. Resultat med sammenligning av feeds
  4. Re-import for nye episoder
  5. 301-redirect-info
- API-funksjoner i api.ts
- Navigasjonslenke i admin-panelet
2026-03-19 00:19:24 +00:00
07d783d572 Starter oppgave 30.7 2026-03-19 00:10:30 +00:00
2cc389e7f5 synops-import-podcast: importer podcast fra RSS-feed (oppgave 30.6)
CLI-verktøy som parser RSS-feed, laster ned lydfiler og artwork til
CAS, og oppretter content-noder med has_media/belongs_to/og_image-edges.

Funksjoner:
- Duplikatdeteksjon via <guid> — idempotent ved gjentatt kjøring
- --dry-run for forhåndsvisning uten skriving til DB/CAS
- Metadata: tittel, beskrivelse, pubDate, duration, episode/season-nummer
- Lydfil → CAS → media-node + has_media-edge
- Artwork → CAS → media-node + og_image-edge
- publish_at satt fra pubDate i belongs_to-edge metadata
- --payload-json for jobbkø-integrasjon med maskinrommet
- JSON-output til stdout med detaljert per-episode status

Testet med The Daily (2801 episoder) og Huberman Lab (389 episoder)
i dry-run modus — parser korrekt inkl. episode-nummerering.
2026-03-19 00:06:37 +00:00
10d3249a6a Starter oppgave 30.6 2026-03-18 23:55:39 +00:00
4c1c470ed7 Embed podcast-spiller: /pub/{slug}/{episode}/player (oppgave 30.5)
Ny maskinrommet-handler som serverer en selvstående HTML-side med
podcast-spiller, designet for iframe-embedding på eksterne nettsider.

Spilleren inkluderer:
- Artwork (episode-spesifikk med fallback til samlingens)
- Tittel og podcast-navn
- Play/pause med loading-spinner
- WaveSurfer.js waveform-visualisering (CDN)
- Tidsvisning (nåværende/total)
- Kapittelmerkering (visuelt på waveform + klikkbar liste)
- Responsiv design (mobil-vennlig ned til 360px)
- Iframe-vennlige headers (X-Frame-Options, CSP frame-ancestors)

Rute: GET /pub/{slug}/{episode_id}/player
Registrert før {article_id} catch-all i rutehierarkiet.
2026-03-18 23:53:21 +00:00
4b53adefa9 Starter oppgave 30.5 2026-03-18 23:45:32 +00:00
3e57adce46 Podcast-statistikk dashboard i admin-panelet (oppgave 30.4)
Nytt dashboard under /admin/podcast-stats som viser:
- Nøkkeltall: totale nedlastinger, unike lyttere, antall episoder
- Daglig trend med horisontale bar charts
- Topp-episoder rangert etter nedlastinger
- Klientfordeling (Apple Podcasts, Spotify, etc.) med stacked bar

Backend: GET /admin/podcast/stats spør podcast_download_stats-tabellen
(fylt av synops-stats CLI fra oppgave 30.3) og aggregerer per episode,
per dag, og per klient via jsonb_each_text.

Filtrering på tidsperiode (7/30/90/365 dager) og enkelt-episode.
2026-03-18 23:42:23 +00:00
e394035a5e Starter oppgave 30.4 2026-03-18 23:35:33 +00:00
86d7002815 synops-stats CLI: podcast-nedlastingsstatistikk fra Caddy-logger (oppgave 30.3)
Nytt CLI-verktøy som parser Caddy JSON access-logger for /media/cas/*
requests og aggregerer nedlastinger per episode per dag.

IAB-compliance:
- Filtrerer 40+ kjente bot user-agents (Googlebot, scrapers, crawlers)
- Unik IP per episode per 24t-vindu (dag-basert deduplisering)

Output: JSON med episode_id, cas_hash, date, downloads, unique_listeners,
og klient-fordeling (Apple Podcasts, Spotify, Overcast, etc.)

--write oppretter podcast_download_stats-tabell i PG med UPSERT
(cas_hash + date som unik nøkkel). Beriker med episode-info fra
has_media-edges når tilgjengelig.
2026-03-18 23:30:45 +00:00
43db2c5d00 Starter oppgave 30.3 2026-03-18 23:24:22 +00:00
6aeb8aa783 Podcast-trait admin-UI og utvidet RSS-metadata (oppgave 30.2)
Dedikert admin-UI for podcast-trait med riktige skjemafelt:
- iTunes Author, Category (med underkategori-dropdown), Language
- Explicit-avkrysning, Redirect Feed URL
- Erstatter generisk nøkkel/verdi-editor for podcast-traitet

RSS-utvidelser:
- itunes:category støtter nå nested subcategory-element
- itunes:new-feed-url for feed-migrasjon via redirect_feed
- Oppdatert både maskinrommet og synops-rss CLI-verktøy
2026-03-18 23:21:15 +00:00
84396dc805 Starter oppgave 30.2 2026-03-18 23:15:30 +00:00
d7f08d439d iTunes/Podcasting 2.0 RSS-tags: komplett implementering (oppgave 30.1)
Utvider synops-rss og maskinrommet/src/rss.rs med iTunes og Podcasting 2.0
namespace for podcast-samlinger.

Channel-level tags:
- itunes:author, itunes:category, itunes:explicit fra podcast-trait metadata
- itunes:image fra samlingens og_image-edge (CAS-hash)
- itunes:type (episodic)
- podcast:locked

Item-level tags:
- itunes:title, itunes:duration (fra media-metadata duration_secs)
- itunes:explicit (arver fra kanal), itunes:image (episode og_image)
- podcast:transcript (SRT-URL hvis transcription_segments finnes)
- podcast:chapters (JSON-URL hvis chapter-edges finnes)

DB-spørringene er utvidet til å hente transkripsjons-eksistens,
varighet, episode-bilde og kapitler i effektive batch-spørringer.

Merk: Transcript/chapters-URL-ene genereres i feeden men krever
offentlige endepunkt for å serveres (fremtidig oppgave).
2026-03-18 23:12:34 +00:00