From 8ca98322488c1c75db6c0e207e736a28eb39c519 Mon Sep 17 00:00:00 2001 From: vegard Date: Tue, 17 Mar 2026 04:54:17 +0100 Subject: [PATCH] Legg til ops/ (vedlikeholdsjobber) og docs/retninger/ (arkitektoniske teser) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ny mappe ops/ med repeterbare vedlikeholdsjobber: - ryddejobb.md — full prosjektrevisjon - doc-audit.md — docs vs kode - drift-sjekk.md — prod vs lokal vs docs Ny mappe docs/retninger/ med arkitektoniske teser: - status_quo.md — hva Sidelinja er i dag - rom_ikke_forum.md — opplevelse-først, to-lags-modell, administrativ opplevelse - universell_input.md — tre primitiver (input, mottak, kommunikasjon), noder+edges - maskinrommet.md — Rust-orkestrator, edge-drevet ressursorkestrering, CAS+pruning - bruker_ikke_workspace.md — brukeren er sentrum, workspaces er samlings-noder - datalaget.md — PG+Apache AGE, SpacetimeDB som sanntidslag, lagmodell Oppdatert CLAUDE.md og proposals/README.md med referanser. Co-Authored-By: Claude Opus 4.6 --- CLAUDE.md | 9 +- docs/proposals/README.md | 3 +- docs/retninger/README.md | 36 +++ docs/retninger/bruker_ikke_workspace.md | 127 ++++++++++ docs/retninger/datalaget.md | 175 ++++++++++++++ docs/retninger/maskinrommet.md | 301 ++++++++++++++++++++++++ docs/retninger/rom_ikke_forum.md | 211 +++++++++++++++++ docs/retninger/status_quo.md | 72 ++++++ docs/retninger/universell_input.md | 299 +++++++++++++++++++++++ ops/README.md | 20 ++ ops/doc-audit.md | 46 ++++ ops/drift-sjekk.md | 45 ++++ ops/ryddejobb.md | 65 +++++ 13 files changed, 1407 insertions(+), 2 deletions(-) create mode 100644 docs/retninger/README.md create mode 100644 docs/retninger/bruker_ikke_workspace.md create mode 100644 docs/retninger/datalaget.md create mode 100644 docs/retninger/maskinrommet.md create mode 100644 docs/retninger/rom_ikke_forum.md create mode 100644 docs/retninger/status_quo.md create mode 100644 docs/retninger/universell_input.md create mode 100644 ops/README.md create mode 100644 ops/doc-audit.md create mode 100644 ops/drift-sjekk.md create mode 100644 ops/ryddejobb.md diff --git a/CLAUDE.md b/CLAUDE.md index faad128..769eb60 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -51,6 +51,13 @@ CLAUDE.md er eneste startdokument. Alt annet ligger under `docs/`: - `synkronisering.md` — PostgreSQL ↔ SpacetimeDB dataflyt og eierskapsmodell - `api_grensesnitt.md` — Kommunikasjonskart: SvelteKit er web-API, Rust er worker - `ai_gateway.md` — LiteLLM som sentralisert AI-ruter (BYOK + OpenRouter fallback) +- `docs/retninger/` — Store arkitektoniske teser og retningsspørsmål (se `README.md` for oversikt): + - `status_quo.md` — Hva Sidelinja er i dag: ambisiøse primitiver, tradisjonell overflate + - `rom_ikke_forum.md` — Bør Sidelinja være et sanntidsrom fremfor en tradisjonell webapp? + - `universell_input.md` — Én multimodal input/mottak-primitiv, noder + edges i stedet for separate tabeller + - `maskinrommet.md` — Rust-tjenestelaget: fang, prosesser, lever — fast grensesnitt for alle tekniske tjenester + - `bruker_ikke_workspace.md` — Brukeren er sentrum, workspaces er frivillige samlings-noder + - `datalaget.md` — PG + Apache AGE som enhetlig graf og arkiv, migreringsstrategi - `docs/proposals/` — Halvtenkte idéer og kreative innfall (se `README.md` for oversikt) - `docs/erfaringer/` — Lærdommer fra implementering (feller, anti-patterns, løsninger): - `svelte5_reaktivitet.md` — $state-getters, SSR-feller, polling-mønster @@ -81,7 +88,7 @@ CLAUDE.md er eneste startdokument. Alt annet ligger under `docs/`: - Tunge AI-jobber (Whisper, LLM-kall) skal aldri blokkere web-requests - All AI-kode peker på `http://ai-gateway:4000/v1` — aldri direkte til leverandør-APIer - Kod og test lokalt i WSL2, deploy via push til Forgejo + SSH pull -- Sjekk alltid relevant doc i `docs/concepts/`, `docs/features/` eller `docs/infra/` før du implementerer +- Sjekk alltid relevant doc i `docs/concepts/`, `docs/features/`, `docs/infra/` eller `docs/retninger/` før du implementerer - Sjekk `docs/erfaringer/` for kjente feller før du implementerer med Svelte 5, SpacetimeDB eller adapter-mønsteret - Etter ferdig implementering av en komponent: dokumenter lærdommer i `docs/erfaringer/` diff --git a/docs/proposals/README.md b/docs/proposals/README.md index fb25f6f..97de915 100644 --- a/docs/proposals/README.md +++ b/docs/proposals/README.md @@ -5,11 +5,12 @@ Halvtenkte idéer, kreative innfall og ting vi vil utforske når vi får tid. Ik ## Pipeline ``` +retninger/ → påvirker alt (arkitektoniske teser) proposals/ → features/ eller concepts/ (idé) (spesifisert, klar for implementering) ``` -Når en idé modnes nok til å bli implementert, skrives en full spec i `docs/features/` eller `docs/concepts/` og forslaget slettes herfra. +Når en idé modnes nok til å bli implementert, skrives en full spec i `docs/features/` eller `docs/concepts/` og forslaget slettes herfra. Idéer som er for store og fundamentale for proposals — arkitektoniske teser om prosjektets retning — hører hjemme i `docs/retninger/`. ## Oversikt diff --git a/docs/retninger/README.md b/docs/retninger/README.md new file mode 100644 index 0000000..b928f4e --- /dev/null +++ b/docs/retninger/README.md @@ -0,0 +1,36 @@ +# Retninger + +Store, åpne spørsmål om prosjektets identitet og arkitektoniske retning. + +Dette er ikke features, ikke proposals, ikke spesifikasjoner — det er **teser** som +utforsker hvordan Sidelinja bør tenke om seg selv. En retning kan påvirke alt fra +teknologivalg til UX-filosofi, men den er ikke en beslutning. Den er en pågående +diskusjon. + +## Pipeline + +``` +retninger/ → kan informere alt: +(tese) concepts/, features/, infra/, arkitektur.md +``` + +En retning "forfremmes" ikke — den modnes, og det den konkluderer med påvirker +andre dokumenter. En retning kan også forkastes eller parkeres. + +## Oversikt + +| Retning | Status | Kjernespørsmål | +|---------|--------|----------------| +| [Status quo](status_quo.md) | Referanse | Hva er Sidelinja i dag? Ankerpunkt for de andre retningene. | +| [Rom, ikke forum](rom_ikke_forum.md) | Åpen | Bør Sidelinja være en oppslukende sanntidsopplevelse fremfor en tradisjonell webapp? | +| [Universell input og mottak](universell_input.md) | Åpen | Én multimodal input-primitiv + personlig mottaksflate, noder + edges i stedet for separate tabeller | +| [Maskinrommet](maskinrommet.md) | Åpen | Én Rust-tjeneste med fast grensesnitt for alle tekniske tjenester: fang, prosesser, lever | +| [Bruker, ikke workspace](bruker_ikke_workspace.md) | Åpen | Brukeren er sentrum, workspaces er frivillige samlings-noder — ikke containere | +| [Datalaget](datalaget.md) | Åpen | PG + Apache AGE som enhetlig graf og arkiv, SpacetimeDB som sanntidslag, CAS for binærdata | + +## Format +- Hva er tesen? +- Hva motiverer den? (observasjoner, frustrasjoner, inspirasjon) +- Hva ville vært annerledes hvis vi fulgte den? +- Spenninger og åpne spørsmål +- Ingen krav om konklusjon diff --git a/docs/retninger/bruker_ikke_workspace.md b/docs/retninger/bruker_ikke_workspace.md new file mode 100644 index 0000000..4cd8932 --- /dev/null +++ b/docs/retninger/bruker_ikke_workspace.md @@ -0,0 +1,127 @@ +# Bruker, ikke workspace + +> Primærenheten er brukeren og brukerens edges. Workspaces er ikke +> containere — de er frivillige samlings-noder som gir felles kontekst. + +## Observasjoner + +I dag er workspaces den organisatoriske enheten. Du "er i" et workspace, +og det bestemmer hva du ser: kanaler, boards, kalendre. Vil du se noe +fra et annet workspace må du bytte. Det er en container-modell — ting +*bor* i workspaces. + +Men i node+edge-modellen gir dette ikke mening. En node er ikke "i" noe +— den har edges til ting. Og brukeren er ikke "i" et workspace — brukeren +har edges til noder, personer, topics, samtaler. + +## Tesen + +**Brukeren er sentrum.** Du logger inn og ser dine edges — alt du er +koblet til. Ikke "velg workspace" som første handling, men "her er alt +ditt." + +### Workspace som samlings-node, ikke container + +Et workspace eksisterer fortsatt som konsept, men det er en node i +grafen — ikke en organisatorisk boks: + +- **Tradisjonell modell:** Workspace inneholder kanaler, boards, filer. + Brukeren er "i" workspacet. +- **Node-modell:** Workspace er en node. Noder har edge til den. Brukeren + har edge til den. Workspace-noden bærer felles kontekst (tema, + pruning-profil, AI-konfig). + +En node kan ha edge til flere workspaces. En faktoid om en gjest er +relevant for både podcast-prosjektet og research-samlingen. Ikke kopi — +samme node, to edges. + +### Personlig er default + +Alt uten workspace-edge eller mottaker-edge er privat — tilhører bare +deg. "Personlig workspace" er ikke en egen ting du oppretter. Det er +fravær av deling. Dine private notater, dagbok, voice memos — de har +bare edges til deg. + +### Administrasjon er edges med roller + +Brukerstyring forvaltes i nodene selv, ikke i et sentralt admin-panel: + +| Edge-type | Hva den gir | +|-----------|------------| +| Eier-edge | Full kontroll — slette, endre tilgang, endre innstillinger | +| Admin-edge | Kan invitere/fjerne andre, endre konfigurasjon | +| Deltaker-edge | Kan gi input og motta | +| Leser-edge | Kan kun motta (observatør, lytter) | + +Du oppretter en kommunikasjonsnode (podcast-prosjekt). Du er eier +automatisk. Du inviterer Trond → deltaker-edge. Trond kan gi input +men ikke slette eller endre tilgang. Du gir Trond admin-edge → nå +kan han invitere andre og endre innstillinger på den noden. + +Dette skalerer fra en privat notat (kun eier-edge til deg) til en +organisasjon (samlings-node med mange admin- og deltaker-edges). + +### Brukeropplevelsen + +Når du logger inn ser du: +- **Dine aktive samtaler** — kommunikasjonsnoder med edge til deg +- **Dine noder** — alt du har skapt eller er koblet til +- **Dine samlings-noder** (det som før var workspaces) — grupperer + kontekst, men du trenger ikke "gå inn i" dem +- **Din mottaksflate** — alt som er relevant for deg nå, vektet + +Du kan filtrere etter samlings-node hvis du vil fokusere ("vis bare +podcast-prosjektet"), men det er et filter — ikke en modebytte. + +### Felles kontekst på samlings-noder + +En samlings-node (workspace) bærer kontekst som arves av tilknyttede +noder: + +- **Pruning-profil** — hvor aggressivt slettes binærdata? +- **Tema** — visuelt uttrykk (CSS custom properties) +- **AI-konfigurasjon** — hvilke prompts, hvilken modell, hvilke regler +- **Tilgangsnivå** — default synlighet for nye noder opprettet i + denne konteksten +- **Kapasitet** — ressursgrenser for maskinrommet (f.eks. maks + samtidige transkripsjoner) + +## Implikasjoner + +### Kryssgående noder er naturlig +En node med edge til to samlings-noder arver kontekst fra begge. +Konflikter (ulik pruning-profil) løses med prioriteringsregler: +mest konservativ vinner, eller eier-edge bestemmer. + +### Onboarding forenkles +Ny bruker trenger ikke "settes opp i et workspace." De får edges til +de nodene de trenger tilgang til. Ferdig. Endre tilgang = endre edges. + +### Forlate et prosjekt er å fjerne edges +Ingen "slett bruker fra workspace." Fjern deltaker-edges. Brukerens +private noder som hadde edge til samlings-noden beholder den edgen +(det er brukerens innhold), men de mister tilgang til andres noder. + +## Spenninger og åpne spørsmål + +- **Overblikk.** Uten workspaces som organisatorisk enhet — hvordan + unngår du at alt flyter sammen? Samlings-noder er svaret, men de + må være intuitive å opprette og bruke uten å bli "workspaces med + ny navn." +- **Tilgangskontroll i praksis.** Edge-basert tilgang er fleksibelt, + men kan det bli uoversiktlig? "Hvem har tilgang til hva" må være + lett å besvare. Kanskje samlings-noden gir en naturlig audit-visning. +- **Arv og konflikter.** En node med edge til to samlings-noder med + ulike pruning-profiler — hva vinner? Trenger klare regler som er + intuitive for brukeren. +- **Migrering.** Eksisterende workspace-modell har innhold. Kan + workspaces bli samlings-noder gradvis? + +## Forhold til andre retninger + +- [Rom, ikke forum](rom_ikke_forum.md) — "rommet" er ikke et workspace, + det er summen av dine edges +- [Universell input og mottak](universell_input.md) — mottaksflaten er + "noder med edge til *meg*", ikke "noder i *mitt workspace*" +- [Maskinrommet](maskinrommet.md) — leser samlings-node-edges for + kontekst (pruning, kapasitet, AI-konfig) diff --git a/docs/retninger/datalaget.md b/docs/retninger/datalaget.md new file mode 100644 index 0000000..d8fcf19 --- /dev/null +++ b/docs/retninger/datalaget.md @@ -0,0 +1,175 @@ +# Datalaget — PG + AGE som enhetlig graf og arkiv + +> PostgreSQL er den eneste databasen. Apache AGE gir Cypher-semantikk +> for graftraverseringer. SpacetimeDB er sanntidslaget over samme data. +> Én database, to tilgangsmønstre, ingen eierskapskonflikt. + +## Observasjoner + +Hele retningsarbeidet har bygget seg mot "alt er noder og edges." +Tilgang er edges, visninger er spørringer, administrasjon er +traversering. Spørsmålet er: hvilken teknologi støtter dette best? + +### Neo4j ble vurdert og forkastet + +Neo4j er best-in-class for dype graftraverseringer, men for dette +prosjektet gir den mer problemer enn den løser: + +- **Tredje database.** PG + SpacetimeDB + Neo4j = tre systemer å + drifte, sikkerhetskopiere og overvåke. På én Hetzner VPS. +- **Mister PG-økosystemet.** pgvector (semantisk søk), fulltekstsøk, + JSONB, pg_cron, statistikk-aggregeringer — alt dette trenger vi + uansett, og det lever i PG. Med Neo4j trenger vi PG *i tillegg*. +- **Minnehungrig.** Neo4j anbefaler 8-16GB heap. På en delt VPS med + PG, SpacetimeDB, Whisper og LiteLLM er det for mye. +- **Lisens.** Community er AGPLv3. Enterprise-features (clustering, + avansert sikkerhet) krever betalt lisens. + +### Apache AGE — Cypher i PG + +Apache AGE er en PG-extension som legger til openCypher-støtte. Noder +og edges lagres i PG-tabeller, men spørres med Cypher-semantikk. + +**Oppsider:** +- **Én database.** Ingen ny infrastruktur. Samme backup, connection + pooling, tooling, monitoring. +- **SQL + Cypher i samme spørring.** "Finn alle noder brukeren har + tilgang til via team-traversering, JOINet med fulltekstsøk og + pgvector." Det kan du ikke gjøre med en separat grafdatabase. +- **Null migrasjonskostnad.** Eksisterende nodes/edges-tabeller kan + eksponeres som graf. Legg til extension, ikke bytt database. +- **Økosystemet bevares.** pgvector, fulltekstsøk, JSONB, pg_cron — + alt fungerer side om side med Cypher-spørringer. +- **Produksjonsbruk.** Azure og EDB tilbyr AGE som managed extension. + +**Nedsider:** +- **Ikke native graf.** Under panseret er det PG-tabeller. For svært + dype traverseringer (10+ hopp over millioner av noder) er Neo4j + raskere. Men de fleste spørringene våre er 1-5 hopp. +- **Yngre prosjekt.** Mindre community, tynnere dokumentasjon enn Neo4j. +- **Quirks.** Spørringer må være enten read-only eller write-only + (WITH-clause for overgang). Noen pg_upgrade-begrensninger. + +## Beslutning + +**PostgreSQL + Apache AGE** som enhetlig datalager for noder og edges. + +De fleste spørringer i dette systemet er grunne: +- "Vis noder med edge til denne brukeren" — 1 hopp +- "Sjekk tilgang via team" — 2 hopp +- "Finn alle kommunikasjonsnoder brukeren har tilgang til" — 2-3 hopp +- "Traverser kunnskapsgrafen mellom to emner" — 3-5 hopp + +AGE håndterer dette. Skulle det vise seg at dypere traverseringer +blir en flaskehals *i praksis*, kan Neo4j evalueres da — men med +én VPS og realistiske datamengder er det usannsynlig. + +## Lagmodell + +``` +┌─────────────────────────────────────┐ +│ GUI (SvelteKit) │ +│ Primitiver, visninger │ +└────────┬──────────────────┬─────────┘ + │ skriv │ les (sanntid) + │ │ direkte WebSocket +┌────────▼────────┐ ┌─────▼─────────┐ +│ Maskinrommet │ │ SpacetimeDB │──┐ +│ (Rust) │ │ (STDB) │ │ +│ Orkestrering │ └───────────────┘ │ +└──┬─────┬─────┬──┘ │ + │ │ │ sync ↕ │ + ▼ ▼ ▼ │ +┌─────┐┌─────┐┌─────┐┌─────────────┐ │ +│ PG ││STDB ││ CAS ││ Whisper, │ │ +│+AGE ││(skr)││ ││ LiteLLM, │ │ +│ ││ ││ ││ LiveKit ... │ │ +└─────┘└─────┘└─────┘└─────────────┘ │ +``` + +### Skriv vs les — to stier med god grunn + +**Skriv:** GUI → Maskinrommet (Rust) → tjenester (PG, STDB, CAS, ...) + +All orkestrering, edge-logikk, ressursallokering og validering går +gjennom Rust. Maskinrommet bestemmer hva som skjer: hva som skrives +til PG, hva som speiles til SpacetimeDB, hva som lagres i CAS, +hvilke tjenester som trigges. + +**Les (sanntid):** SpacetimeDB → GUI (direkte WebSocket) + +SpacetimeDB sitt klient-SDK kobler seg direkte via WebSocket, +definerer SQL-subscriptions, og synkroniserer automatisk med lokal +cache — uten network round-trips for lesing (~10μs per transaksjon). +Å proxy dette gjennom Rust ville lagt til et hopp, serialisering og +kontekstbytte — en dårligere reimplementering av noe STDB gjør +optimalt. Den direkte lese-stien er en bevisst arkitekturbeslutning, +ikke et hull i lagmodellen. + +**Les (tradisjonelt):** GUI → Maskinrommet (Rust) → PG + +Søk, historikk, statistikk, arkiv — alt som ikke er sanntid går +gjennom Rust og PG. AGE-spørringer for graftraversering, pgvector +for semantisk søk, SQL for aggregeringer. + +### PG er arkivet og grafen +Alle noder og edges lever i PG. AGE gir Cypher-spørringer for +traversering. Standard SQL for alt annet (aggregeringer, fulltekstsøk, +JOINs mot pgvector). Én database, to spørrespråk som utfyller +hverandre. + +### SpacetimeDB er sanntidslaget +Aktive noder og edges speilet til SpacetimeDB for live-oppdateringer. +Ting som er "nå" lever i SpacetimeDB. Ting som er "ferdig" lever kun +i PG. Ingen eierskapskonflikt — SpacetimeDB er en live-visning av +en delmengde av PG-grafen. + +### CAS er binærlageret +Lyd, bilde, video lagres content-addressable utenfor PG. Noder i +PG peker på CAS-hasher. Pruning-regler basert på edges, aksesslog +og modalitet (se maskinrommet). + +## Migreringsstrategi + +### Fase 1: AGE på eksisterende PG +Installer Apache AGE extension. Eksponer eksisterende nodes/edges- +tabeller som graf. Ingen endring for resten av systemet — bare en +ny måte å spørre på. + +### Fase 2: Konsolider til node+edge-modellen +Migrer meldingsboks, kanban, kalender etc. til den universelle +node+edge-modellen gradvis. Gamle tabeller kan leve som views +over grafen i overgangsperioden. + +### Fase 3: Parallell prototype +Det gamle systemet kjører uforstyrret. En ny frontend-prototype +bygges ved siden av som bruker AGE-grafen direkte. Live sync fra +gammelt til nytt via enveis oppdateringer. Testbrukere leker i +det nye, produksjon er trygt i det gamle. + +### Fase 4: Cutover +Når det nye er godt nok, flyttes trafikk over. Det gamle systemet +kan kjøre som read-only fallback en periode. + +## Spenninger og åpne spørsmål + +- **AGE-modenhet.** Prosjektet er aktivt og støttet av Azure/EDB, + men community er mindre enn Neo4j. Risikoen er håndterbar fordi + dataene alltid er PG-tabeller — AGE er bare et spørrelag. +- **SpacetimeDB-synk.** Hvordan synkes noder/edges mellom PG (AGE) + og SpacetimeDB effektivt? Trenger en sync-mekanisme som forstår + "aktiv delmengde." +- **Skjemadesign.** Én node-tabell med alle typer innhold — hva er + felles kolonner vs edge-metadata vs JSONB-payload? Trenger + gjennomtenkt skjema som balanserer fleksibilitet og spørrbarhet. + +## Forhold til andre retninger + +- [Universell input og mottak](universell_input.md) — noder og edges + er den underliggende datamodellen for alle tre primitiver +- [Maskinrommet](maskinrommet.md) — CAS og pruning lever her, AGE + brukes for edge-drevet ressursorkestrering +- [Bruker, ikke workspace](bruker_ikke_workspace.md) — tilgang via + graf-traversering (AGE) i stedet for workspace-membership-tabeller +- [Rom, ikke forum](rom_ikke_forum.md) — to-lags-modellen: + SpacetimeDB for nåtid, PG+AGE for arkiv og graf diff --git a/docs/retninger/maskinrommet.md b/docs/retninger/maskinrommet.md new file mode 100644 index 0000000..b59f56c --- /dev/null +++ b/docs/retninger/maskinrommet.md @@ -0,0 +1,301 @@ +# Maskinrommet — teknisk tjenestelaget + +> Én Rust-tjeneste med et fast grensesnitt. Alle tekniske tjenester beveger +> seg gjennom dette laget. Fang, prosesser, lever. + +## Observasjoner + +I dag er tekniske tjenester spredt: +- **Worker** (Rust) — kjører bakgrunnsjobber +- **Jobbkø** (PG) — koordinerer arbeid +- **AI Gateway** (LiteLLM) — ruter AI-kall +- **Whisper** — transkripsjon +- **LiveKit** — lyd/video-strømmer + +Hver har sitt eget grensesnitt. Frontend og primitiv-laget må vite hva +som finnes under panseret. Det er ingen felles abstraksjon, ingen felles +logging, ingen felles kapasitetsstyring. + +## Tesen + +Alt som krever tunge ressurser eller eksterne tjenester går gjennom +**ett lag** med **ett grensesnitt**. Ikke fordi det er elegant — fordi +det gir et fast punkt som er enkelt å fange, modifisere og forbedre. + +Maskinrommet gjør tre ting: + +### 1. Fang (input-absorpsjon) +Ta imot råmateriale i alle modaliteter: +- Tekst (melding, URL, dokument) +- Lyd (voice memo, live stream, filopplasting) +- Bilde (foto, skjermbilde, tegning) +- Video (stream, opptak) +- Strukturert data (JSON, metadata, edges) + +### 2. Prosesser (transformasjon) +Analyser, transformer, berik og systematiser: +- **STT** — lyd → tekst (Whisper) +- **TTS** — tekst → lyd (ElevenLabs / lokal modell) +- **AI-analyse** — oppsummering, klassifisering, sentimentanalyse, + faktasjekk, edge-forslag +- **Beriking** — URL → metadata, bilde → beskrivelse, lyd → segmenter +- **Søk** — fulltekst, semantisk (pgvector), graftraversering +- **Mediaprosessering** — transcode, thumbnail, waveform + +### 3. Lever (output-distribusjon) +Lever resultat i riktig modalitet til riktig mottaker: +- Tekst (melding, notifikasjon, digest) +- Lyd (TTS-opplesning, lydstream) +- Video/bilde (stream, thumbnail, snapshot) +- Strukturert data (noder, edges, metadata tilbake i grafen) +- Push (webhook, SSE, SpacetimeDB-reducer) + +## Edge-drevet ressursorkestrering + +Nøkkelinnsikten: **maskinrommet leser edges for å vite hva det skal gjøre.** +Noden selv er alltid enkel. Det er edgene som bestemmer hvilke ressurser +som spinnes opp. + +### Security by default +Input uten mottaker-edge er automatisk privat. Du trenger ikke "velge +privat" — det er utgangspunktet. Ingen ser det. Ingen ressurser kobles +inn utover det grunnleggende (fang + transkriber). Privat er ikke en +innstilling, det er fravær av deling. + +### Ressurser er proporsjonale med edges +Samme nodetype, vilt forskjellig ressursbruk: + +``` +Dagboknotat (privat voice memo): + node → fang lyd → transkriber (Whisper) → lagre + Ressurser: minimal + +Samtale med Trond: + node + mottaker-edge(Trond) + → fang lyd → transkriber → lever tekst/lyd til Trond + Ressurser: STT + levering til én + +Redaksjonsmøte (5 deltakere): + node + mottaker-edges(5) + rolle-edges + → fang lyd fra alle → transkriber → lever til alle → AI-referent + Ressurser: STT + levering til 5 + LLM + +Livesending (1000 lyttere): + node + mottaker-edges(∞) + stream-edge + publiserings-edge + → fang lyd → transkriber → stream via LiveKit → distribuer + → generer segmenter → kjør live AI → publiser + Ressurser: STT + LiveKit + LLM + mediaprosessering +``` + +Maskinrommet gjør ikke mer enn det edges krever. Ingen overhead for +enkle ting. Noden vet ingenting om LiveKit — den har bare edges som +sier "stream til disse mottakerne", og maskinrommet bestemmer at det +betyr LiveKit. + +### Naturlig eskalering +Du starter en privat voice-note. Bestemmer deg for å dele den med Trond +→ legg til mottaker-edge, maskinrommet begynner å levere. Trond foreslår +at dere tar det som et møte → legg til flere deltaker-edges, maskinrommet +kobler inn sanntidsstrømming. Møtet blir en innspilling → legg til +publiserings-edge, maskinrommet aktiverer produksjonspipeline. + +Hvert steg er bare å legge til edges. Maskinrommet reagerer og kobler +inn flere ressurser etter hvert. Ingen migrering, ingen modebytte. + +## Grensesnittet + +Maskinrommet eksponerer et konsistent API — sannsynligvis en Rust trait +eller et sett traits: + +``` +fang(input: RåInput) → NodeId +prosesser(node: NodeId, operasjon: Operasjon) → Resultat +lever(node: NodeId, mottaker: Mottaker, format: Format) → Status +``` + +Men i praksis er mye av dette *reaktivt*: maskinrommet observerer +edge-endringer og handler automatisk. Legger noen til en mottaker-edge +→ maskinrommet begynner å levere. Legger noen til en stream-edge → +maskinrommet kobler inn LiveKit. Primitivene trenger ikke eksplisitt +kalle `lever()` — de manipulerer edges, og maskinrommet reagerer. + +## Hva dette gir + +### Isolasjon +Bytt Whisper med noe annet? Endre maskinrommet. Frontend vet ingenting. +Legg til bildegenerering? Ny operasjon i maskinrommet. Primitivene +kaller den uten å vite hva som skjer under. + +### Observerbarhet +Alt går gjennom ett punkt. Logging, metrikker, kostnadsrapportering, +feilhåndtering — alt på ett sted. "Hva bruker vi AI-ressurser på?" +har ett svar. + +### Kapasitetsstyring +Prioritering, kø, rate limiting, fallback mellom leverandører — alt +håndtert av maskinrommet. En podcastinnspilling som trenger live +transkripsjon kan prioriteres over en bakgrunns-oppsummering. + +### Fast utviklingspunkt +To team (eller to hatter) med klart grensesnitt: +- **Over maskinrommet:** primitiver, noder, edges, UI, brukeropplevelse +- **I maskinrommet:** ytelse, integrasjoner, kapasitet, kostnad + +Du kan perfeksjonere det ene uten å røre det andre. + +## Content-Addressable Storage og intelligent pruning + +Maskinrommet forvalter også lagring. Ikke alt kan lagres for evig — men +ikke alt trenger det heller. Signalene for hva som er viktig finnes +allerede i grafen. + +### CAS som lagringsprimitiv +All binærdata (lyd, bilde, video) lagres i et content-addressable store. +Fordeler: +- **Deduplisering gratis** — samme fil delt i tre kontekster = én kopi +- **Separasjon** — "innholdet eksisterer" er adskilt fra "innholdet er + tilgjengelig." Noden peker på en hash, CAS har filen (eller ikke). +- **Enkel opprydning** — slett hashen fra CAS, alle noder som pekte + dit mister binærdataen men beholder metadata og transkripsjon. + +### Lagringsregler per modalitet + +| Modalitet | Default levetid | Begrunnelse | +|-----------|----------------|-------------| +| Tekst | Evig | Billig, er essensen av innholdet | +| Transkripsjon | Evig | Tekstlig representasjon av lyd/video — tar vare på meningen | +| Lyd | 30 dager | Mellomkostnad, transkripsjon bevarer innholdet | +| Bilde | 30 dager | Mellomkostnad, beskrivelse/metadata bevarer kontekst | +| Video | 14 dager | Dyrest, transkripsjon + thumbnail bevarer det meste | + +### Signaler som forlenger levetid + +Default-TTL er bare utgangspunktet. Maskinrommet justerer basert på: + +- **Edges.** En lydfil med edge til episoderegisteret = publisert + podcast, beholdes. En privat voice-memo uten edges = 30-dagers TTL. +- **Aksesslog.** Hvis noen har spilt av lydfilen i løpet av TTL-perioden, + forlenges den. Ingen aksess = ingen verdi i å beholde binærdataen. +- **Transkripsjonsstatus.** Lyd som er transkribert har "overlevert sin + essens" til tekst. Lyd som *ikke* er transkribert (f.eks. musikk, + lydeffekter) kan trenge lengre TTL. +- **Edge-type.** Edge til publisert innhold = behold. Edge til arkivert + møte = transkripsjon holder. Edge til ingenting = teksten lever videre, + binærdataen kan dø. + +### Eksempler + +``` +Privat voice-memo, aldri delt: + → Lyd transkriberes → tekst lagres evig + → Lydfil: 30 dager, ingen aksess, ingen edges → slettes + → Noden lever videre med teksten + +Podcastepisode: + → Lyd har edge til episoderegister + publiserings-edge + → Aksesseres regelmessig via podcastarkivet + → Lydfil: beholdes så lenge edges og aksess tilsier det + +Rutinemøte for et år siden: + → Video (6 kanaler): ingen har sett den på 6 måneder → slettes + → Lyd: ingen har spilt av → slettes + → Transkripsjon: tekst, lagres evig. Søkbar, refererbar. + → Noden lever med full kontekst minus binærdata + +Viktig styremøte: + → Video aksesseres av styremedlemmer → forlenges + → Workspace-innstilling: "behold video i 1 år" → overrider default +``` + +### Generert innhold er en cache +TTS, thumbnails, AI-oppsummeringer, waveforms — alt som kan regenereres +fra kildedata er i praksis en cache. Det lagres i CAS med samme TTL- +mekanisme som alt annet: +- Peter ber om lyd-versjon av en tekstmelding → TTS genereres, lagres +- Ingen spiller den av på 30 dager → filen slettes fra CAS +- Peter (eller noen andre) ber om lyd igjen → regenereres on-demand +- Teksten er der alltid. Binærdataen er flyktig. + +Maskinrommet trenger ikke skille mellom "original lyd" (voice memo) og +"generert lyd" (TTS) i pruning-logikken. Begge er binærdata i CAS med +en TTL som forlenges ved aksess. Forskjellen er bare at generert +innhold alltid kan gjenskapes fra kilden — så det er tryggere å prune. + +### Workspace-styrt aggressivitet +Hvert workspace kan justere sin pruning-profil: +- **Konservativt** — behold alt lenge (f.eks. arkiv-workspace) +- **Aggressivt** — tekst bevares, binærdata prunes raskt (f.eks. + daglig drift-workspace med mye rutineinnhold) +- **Tilpasset** — egne regler per modalitet og edge-type + +### Brukerens erfaringsbaserte meny +Over tid bruker du noen edges oftere enn andre, noen noder oftere enn +andre. Maskinrommet observerer dette og tilbyr en erfaringsbasert meny: +dine mest brukte koblinger, dine vanligste input-mønstre, dine +foretrukne modaliteter. Ikke som en rigid konfigurasjon — som en +adaptiv overflate du kan aktivere og deaktivere fortløpende. + +Dette er ikke maskinlæring eller kompleks AI — det er frekvenstelling +på edges og aksesslog. Enkelt å implementere, intuitivt for brukeren. + +## Pragmatisk vei dit + +Ikke bygg dette fra scratch. Formaliser det som allerede finnes: + +1. **Worker + jobbkø er allerede kjernen.** De trenger et konsistent + API, ikke en omskriving. +2. **AI Gateway (LiteLLM) absorberes** — i stedet for en separat proxy, + blir LLM-kall en operasjon i maskinrommet som alt annet. +3. **Whisper, TTS, mediaprosessering** — allerede planlagt som + worker-jobber. Gi dem samme grensesnitt. +4. **LiveKit** — den mest spesielle tjenesten (sanntidsstrømmer). Kan + starte som en separat integrasjon og formaliseres inn over tid. + +Rekkefølge: definer traits → migrer eksisterende worker-jobber inn → +legg til nye tjenester etter hvert. Fast punkt fra dag én, full +dekning over tid. + +## Spenninger og åpne spørsmål + +- **Synkron vs asynkron.** "Fang" og "lever" kan være instant, men + "prosesser" kan ta sekunder (TTS) eller minutter (full episode- + transkripsjon). Grensesnittet må håndtere begge naturlig. +- **Strømmer.** Live lyd/video er fundamentalt annerledes enn + request/response. Men edge-modellen løser mye: maskinrommet ser en + stream-edge og vet at det betyr LiveKit. Utfordringen er *reaktivitet* + — maskinrommet må observere edge-endringer i sanntid og koble inn/ut + ressurser dynamisk. +- **Granularitet.** Hvor mye skal maskinrommet vite om domenet? "Fang + lyd" er generisk, men "transkriber og splitt i segmenter med + taler-identifikasjon" er domenespesifikt. Hvor går grensen? +- **Overhead.** Et ekstra lag betyr et ekstra kall. For tunge + operasjoner (Whisper, LLM) er det neglisjerbart. For lette + operasjoner (slå opp metadata) kan det være unødvendig indirection. + +## Plassering i lagmodellen + +Maskinrommet (Rust) er det eneste orkestringslaget. Alle tjenester — +inkludert PG, SpacetimeDB, CAS, Whisper, LiteLLM, LiveKit — er +likeverdige tjenester *under* maskinrommet. SpacetimeDB er ikke et lag +mellom Rust og GUI, det er en tjeneste maskinrommet skriver til. + +Ett unntak: SpacetimeDB har en direkte WebSocket-kobling til frontend +for sanntids lese-strøm. Dette er en bevisst optimering — STDB sitt +klient-SDK gir ~10μs-oppdateringer med automatisk synk og lokal cache. +Å proxy dette gjennom Rust ville vært å bygge en dårligere versjon av +noe STDB gjør optimalt. + +Se [datalaget](datalaget.md) for full lagmodell med diagram. + +## Forhold til andre retninger + +Maskinrommet er infrastrukturen *under* de tre primitivene i +[universell input og mottak](universell_input.md): +- Input-primitiven kaller `fang()` + `prosesser()` +- Mottak-primitiven kaller `lever()` +- Kommunikasjonsnoden bruker alle tre (fang input fra deltakere, + prosesser sanntid, lever til mottakere) + +Det er også det som gjør to-lags-modellen fra [rom, ikke forum](rom_ikke_forum.md) +praktisk: maskinrommet ruter til riktig lag (sanntid vs tradisjonelt) +uten at primitivene trenger å vite forskjellen. diff --git a/docs/retninger/rom_ikke_forum.md b/docs/retninger/rom_ikke_forum.md new file mode 100644 index 0000000..57b017b --- /dev/null +++ b/docs/retninger/rom_ikke_forum.md @@ -0,0 +1,211 @@ +# Rom, ikke forum + +> Hva om Sidelinja ikke er en webapp med sanntidsfunksjoner, men en oppslukende +> sanntidsopplevelse som tilfeldigvis leverer tradisjonell funksjonalitet? + +## Observasjoner + +Sidelinja har vokst organisk. Vi har bygget chat, kanban, kalender, notater, +kunnskapsgraf — hver som sin feature, hver med sin spec. SpacetimeDB ble lagt til +for sanntid, men arkitekturen er fortsatt "PostgreSQL-app med sanntidskrydder." + +Resultatet: +- **Forum-følelsen.** Ting er organisert i tråder, kort, lister. Brukeren + navigerer mellom sider. Det føles som et tradisjonelt verktøy med litt polish. +- **Databasespenning.** PG og SpacetimeDB har et komplisert eierforhold. + SpacetimeDB-loven løser grensesnittet, men ikke det underliggende spørsmålet: + hva *er* primæropplevelsen? +- **Feature-fragmentering.** Chat, kanban, whiteboard, notater — hver lever i sin + boks. "Universell overføring" og "meldingsboks" prøver å lime dem sammen, men + utgangspunktet er fortsatt separate primitiver. + +## Tesen + +Snu gravitasjonen: + +**I stedet for:** Tradisjonell webapp → bolt på sanntid der det trengs +**Tenk:** Sanntidsrom er default → persistens er en egenskap ved ting som skjer + +Forskjellen er subtil men fundamental: +- Et "dokument som flere kan redigere" vs "et rom der folk er sammen og ting + de gjør blir husket" +- "Gå til kanban-brettet" vs "åpne kanban-laget i rommet du allerede er i" +- "Send en melding i chat" vs "si noe i rommet" + +## Hva ville vært annerledes? + +### SpacetimeDB som verden, PG som arkiv +SpacetimeDB er ikke en "sanntidskache foran PG" — det er verdenen brukerne +lever i. PG er arkivet som husker hva som har skjedd. + +Rollene blir klare og adskilte: +- **SpacetimeDB** = sanntidslaget. Aktivt samarbeid, live interaksjon, + ting som skjer *nå*. +- **PostgreSQL** = arkivet. Alt som noensinne har skjedd. Søk, historikk, + statistikk, revisjon. + +Men viktig: sanntidslaget er *bare* et sanntidslag. Ikke alt trenger +sanntid. En kunnskapsgraf-utforsker, et søk i gamle episoder, en +statistikkside, en offentlig publisert artikkel — disse snakker rett med +PG-arkivet som tradisjonelle nettsider. De trenger ikke gå gjennom +SpacetimeDB. Begge lagene leser og skriver til det samme arkivet. + +Dermed har vi to parallelle overflater: +- **Sanntidsopplevelsen** (via SpacetimeDB) — for alt som er levende, + aktivt, samarbeidende. Chat, whiteboard, live redigering. +- **Tradisjonelt lag** (rett mot PG) — for alt som er retrospektivt, + utforskende, statisk. Arkiv, søk, publisering, statistikk. + +Dataflyt mellom dem: ting som oppstår i sanntidslaget synkes til PG. +Ting i PG kan løftes inn i sanntidslaget når de blir aktive igjen. +Men det er ingen eierskapskonflikt — de to lagene har fundamentalt +forskjellige roller. Det er ikke to konkurrerende sannheter, det er +*nåtid* og *arkiv*, med to overflater som passer til hver sin rolle. + +### Rommet som primitiv, ikke siden +I dag navigerer brukeren mellom `/chat`, `/kanban`, `/kalender`. I "rom"-modellen +er brukeren alltid *et sted*, og funksjonalitet er lag som kan slås av og på: +chat-laget, oppgave-laget, tidslinje-laget. Alt eksisterer i samme sanntidsrom. + +### Tilstedeværelse som førsteklasses konsept +Hvem er her nå? Hva ser de på? Hva jobber de med? I en tradisjonell webapp er +dette en "feature" (online-indikator). I rom-modellen er det fundamentet alt +annet bygger på. + +### Interaksjon før organisering +I dag: lag et kanban-kort → fyll ut feltene → flytt det. I rom-modellen: si noe, +tegn noe, del noe → det som oppstår kan *bli* et kort, en oppgave, en notis — +organisering skjer etterpå, ikke som forutsetning. + +## Den administrative opplevelsen + +Tesen ovenfor kan føles abstrakt. Mer konkret: Sidelinja bør være en +**administrativ opplevelse** — ikke et spill med avatarer, men et arbeidsmiljø +der produktive ting er lette å oppnå og strukturen følger deg, ikke omvendt. + +### Formløs input, struktur etterpå +I dag velger du visning først: "nå er jeg i chat", "nå er jeg i kanban." Men +meldingsboksen er allerede designet for at input ikke trenger å vite hva den +*er* på forhånd. En tanke bør kunne starte som en løs setning og bli et +kanban-kort, en faktoid, en kalenderoppføring — uten at brukeren måtte +bestemme det på forhånd. Visninger er flyktige linser. Innhold er permanent. + +### Trylle frem, legge fra seg +Trenger du et whiteboard? Det dukker opp. En videosamtale? Den starter. +En dagbok? Den er der. Og når fokuset tar en annen retning legger du det +fra deg uten at det blir borte — kunnskapsgrafen holder styr på det. Du +trenger ikke "lukke" noe eller "lagre" noe. Ting eksisterer i verden +og kan gjenfinnes. + +### Siloer forsvinner +"Universell overføring" som eksplisitt feature blir overflødig fordi det +ikke finnes separate steder å overføre *mellom*. Du endrer ikke *hvor* +noe er — du endrer *hvordan* du ser på det som allerede er der. Chat, +kanban, kalender er ikke apper — de er visninger av samme tilstandsrom. + +### Privat og delt som lag, ikke separate systemer +"Personlig workspace" trenger ikke være en egen ting. Privat/delt er bare en +synlighetsbryter på alt du gjør. Du chatter med en kollega og samtidig skriver +du dagbok — begge er meldingsbokser, den ene er delt, den andre er privat. +De eksisterer side om side i samme rom. + +Dette åpner for naturlig flyt mellom kontekster: du sitter på bussen, snakker +inn en tanke via voice, den transkriberes automatisk og lander som en privat +meldingsboks — dagboknotis, oppgavepåminnelse, idé til neste episode. Du +trenger ikke åpne "dagbok-appen" eller "notat-appen." Du bare snakker, og +systemet tar vare på det riktig sted basert på kontekst og synlighet. + +Input-metode (tekst, voice, tegning) og synlighet (privat, delt, publisert) +er ortogonale egenskaper. Ingen av dem bør diktere *hva* innholdet blir. + +### SpacetimeDB som naturlig motor +SpacetimeDB tenker "dette eksisterer i verden nå" — ikke "lagre dette i +riktig tabell." Å trylle frem et whiteboard er naturlig i en verden-modell: +det er bare et nytt objekt med en tilstand. I PG-modellen må du opprette +rader, definere relasjoner, sette opp persistens. SpacetimeDB låner seg +til flytende, formløs interaksjon på en måte PG ikke gjør. + +PG briljerer i rollen som arkiv: relasjonelle spørringer over historikk, +fulltekstsøk, pgvector for semantisk søk, aggregeringer og statistikk. +Når du spør "hva snakket vi om i mars?" er det PG som svarer. Når du +spør "hva skjer nå?" er det SpacetimeDB. + +## Spenninger og åpne spørsmål + +- **Ytelse.** En alltid-på sanntidsopplevelse krever mer av både klient og + server enn tradisjonelle sideinnlastinger. Er SpacetimeDB klar for dette? +- **Kompleksitet.** "Alt er et rom" høres elegant ut, men kan bli kaotisk. + Hvordan unngår vi at det blir uoversiktlig? +- **Discovery vs fokus.** "Alt kan bli hva som helst" er kraftig, men kan + bli overveldende. Et whiteboard du tryller frem må være like lett å finne + igjen om tre uker. Kunnskapsgrafen er kanskje svaret — den er allerede + designet for å koble ting uavhengig av type. +- **~~Gradvis overgang.~~** *(Løst — se "Innebygd utviklingsstrategi" nedenfor.)* +- **Solo-bruk.** Mye av verdien i "rom" kommer fra å være der sammen. Hvordan + føles det for én person som jobber alene? +- **Er det vi allerede har?** Meldingsboks-konseptet, universell overføring og + SpacetimeDB-loven peker allerede i denne retningen. Kanskje dette ikke er en + ny retning, men en artikulering av det vi ubevisst har bygget mot? +- **Inspirasjon.** Spillverdener (MMO-lobbyer, shared spaces), Figma (alle i + samme canvas), tldraw, Gather.town. Hva kan vi lære fra disse? + +## Innebygd utviklingsstrategi + +To-lags-modellen gir en viktig implikasjon for utviklingen: **innebygd fallback +og to veier inn til alt.** + +Ny funksjonalitet kan alltid starte i det tradisjonelle laget — rett mot PG, +vanlig request/response, kjent terreng. Den fungerer med en gang. Når det +senere gir verdi (samarbeid, sanntid, live interaksjon) kan den løftes inn +i sanntidslaget. Men den trenger ikke det for å være nyttig. + +Dette betyr: +- **Ingenting vi har bygget er bortkastet.** Eksisterende PG-baserte features + er ikke "gammel arkitektur" — de er det tradisjonelle laget, og det er et + fullverdig lag. +- **Ingen stor omskriving.** Retningen er ikke en migrasjon med en frist. Den + er en utviklingsstrategi: bygg tradisjonelt, løft til sanntid ved behov. +- **Risikoen er lav.** Hvis SpacetimeDB skuffer, har vi fortsatt et komplett + tradisjonelt system. Hvis det leverer, får ting gradvis en rikere opplevelse. +- **Primitivene er nøkkelen.** Så lenge meldingsboksen og kunnskapsgrafen er + fleksible nok, kan begge lag bruke dem. Arkitekturen holder uavhengig av + hvilket lag en feature lever i. + +## Kritisk vurdering + +### SpacetimeDB er en ung teknologi +Å lene seg tungt på SpacetimeDB er et veddemål. Hvis prosjektet stagnerer, +endrer API, eller har skaleringstak vi ikke ser ennå — er vi eksponert. +*Mildnet* av at det tradisjonelle laget mot PG alltid finnes som fallback: +SpacetimeDB er ikke alt-eller-ingenting, men et lag for det som trenger +sanntid. Resten lever trygt mot PG uansett. + +### "Formløs input" er vanskelig i praksis +Det høres elegant ut, men noen må bestemme hva noe *blir*. AI? Brukeren +etterpå? Automatiske regler? Notion, Roam Research og mange andre startet +med lignende ambisjoner og endte opp med å gi brukeren eksplisitte +strukturverktøy — fordi ambiguitet i praksis skaper friksjon, ikke flyt. +Risikoen er at vi bygger noe som føles magisk i demoen og forvirrende i +hverdagen. + +### Grensen mellom "nåtid" og "arkiv" er uklar +En samtale fra i går som fortsatt er relevant — er den "nå" eller "arkiv"? +Et kanban-kort som har stått stille i to uker? Vi trenger regler for når ting +flyttes mellom lagene, og de reglene kan bli like komplekse som +SpacetimeDB-loven de erstatter. "Tid og arkiv" er renere *i prinsippet*, +men i praksis er "aktiv" et spektrum, ikke en binær tilstand. + +### Solo-bruk er underadressert +Vegard er primærbruker. "Rom"-konseptet henter mye av sin kraft fra +tilstedeværelse og samarbeid. For én person som produserer podcast er det +en reell risiko at sanntidslaget er overhead sammenlignet med et godt +organisert tradisjonelt verktøy. *Mildnet* av to-lags-modellen: solo-bruk +kan lene seg tyngre på det tradisjonelle PG-laget, og sanntidslaget +aktiveres når det faktisk gir verdi (live innspilling, samarbeid). + +### Omfanget +*Betydelig mildnet* av utviklingsstrategien ovenfor. Retningen krever ikke +en omskriving — den er kompatibel med inkrementell utvikling. Den +gjenværende risikoen er mer subtil: at vi bruker mental energi på å +vurdere "bør dette være sanntid?" for hver feature i stedet for å bare +bygge. Pragmatisk default bør være: bygg tradisjonelt, løft senere. diff --git a/docs/retninger/status_quo.md b/docs/retninger/status_quo.md new file mode 100644 index 0000000..44ce58f --- /dev/null +++ b/docs/retninger/status_quo.md @@ -0,0 +1,72 @@ +# Status quo — Hva Sidelinja er i dag + +> En redaksjonell webapp med ambisiøse primitiver og tradisjonell overflate. + +## Hva fungerer + +### Meldingsboksen som universell primitiv +Den viktigste arkitekturbeslutningen. Én datamodell — meldingsboksen — er +underlag for chat, kanban-kort, kalenderoppføringer, notater og faktoider. +I stedet for fem separate domenemodeller har vi én fleksibel primitiv med +view-konfigurasjoner oppå. Dette er genuint uvanlig og gir oss muligheter +de fleste redaksjonelle verktøy ikke har. + +### Kunnskapsgrafen +Nodes og edges i PostgreSQL gir en rik struktur for å koble alt med alt — +personer, temaer, episoder, fakta. Dette er ryggraden i det redaksjonelle +arbeidet og skiller Sidelinja fra enklere verktøy. + +### Self-hosted med full kontroll +Hetzner VPS, Caddy, Authentik, Forgejo. Ingen avhengighet til skytjenester +vi ikke kontrollerer. For et journalistisk verktøy er dette ikke bare +en preferanse — det er et prinsipp. + +### AI som infrastruktur, ikke feature +LiteLLM som gateway, BYOK-modell, Whisper for transkripsjon. AI er ikke +en knapp i UI-et — det er en del av maskineriet. Jobbkø-arkitekturen gjør +at tunge operasjoner aldri blokkerer brukeropplevelsen. + +## Hva som er tradisjonelt + +### Navigasjon +Brukeren beveger seg mellom `/chat`, `/kanban`, `/kalender` som separate +sider. Til tross for at datamodellen er universell, føles opplevelsen +fragmentert — som et sett med separate verktøy som deler database. + +### Interaksjonsmodell +CRUD-mønsteret dominerer: opprett, rediger, slett, flytt. Interaksjonen +er form-basert og eksplisitt. Brukeren "administrerer innhold" mer enn +de "jobber sammen i et miljø." + +### Sanntid som tillegg +SpacetimeDB er lagt til for å gi sanntidsoppdatering, men arkitekturen +er PostgreSQL-først. Sanntid er noe som *skjer med* tradisjonelle +operasjoner, ikke noe som er *grunnlaget* for opplevelsen. + +## Spenninger + +### To sannhetskilder +PG og SpacetimeDB har et komplisert forhold. SpacetimeDB-loven definerer +klare regler for hvem som eier hva, men selve eksistensen av loven vitner +om en arkitektonisk spenning: vi har to systemer som begge vil være +primærkilde, og vi bruker konvensjoner for å holde dem fra å kollidere. + +### Ambisiøs bunn, forsiktig topp +Meldingsboksen og kunnskapsgrafen åpner for opplevelser vi ikke leverer +ennå. Datamodellen sier "alt henger sammen" — men UI-et sier "her er +chatten, her er kanban-brettet, her er kalenderen." Grunnmuren er mer +spennende enn det brukeren ser. + +### Produksjonsverktøy vs opplevelse +Sidelinja er designet for podcast-produksjon, men pendler mellom å være +et effektivt arbeidsverktøy og noe mer oppslukende. Studioet og +møterommet peker mot sanntidsopplevelser. Redaksjonen og kanban peker +mot tradisjonelt prosjektstyringsverktøy. Begge er gyldige, men de +trekker i ulike retninger. + +## Oppsummert + +Sidelinja har en sterkere grunnmur enn overflaten viser. Meldingsboksen, +kunnskapsgrafen og AI-infrastrukturen er genuint interessante primitiver. +Spørsmålet er ikke om vi har bygget feil — men om overflaten utnytter +det fundamentet faktisk tillater. diff --git a/docs/retninger/universell_input.md b/docs/retninger/universell_input.md new file mode 100644 index 0000000..09c8d61 --- /dev/null +++ b/docs/retninger/universell_input.md @@ -0,0 +1,299 @@ +# Universell input og mottak + +> Én multimodal input-primitiv. Én personlig mottaksflate. Alt som fanges +> er samme type objekt. Hva det "er" bestemmes av edges, ikke av tabellen +> det ligger i. Hvordan det *presenteres* bestemmes av mottakeren. + +## Observasjoner + +I dag har vi meldingsboksen som "universell primitiv" — men den er egentlig en +*lagringsprimitiv*. Den samler chat, kanban-kort, kalenderoppføringer og notater +i én tabell, men *input* er fortsatt forskjellig per kontekst: chat har ett +tekstfelt, kanban har et skjema, kalender har en datovelger. Brukeren velger +kontekst først, deretter gir de input. + +Og vi har separate pipelines for ulike modaliteter: tekst går én vei, lyd +(voice/transkripsjon) en annen, bilder en tredje. Hver med sin egen flyt. + +## Tesen + +**Én input-primitiv, to versjoner:** + +- **Sanntidsversjon** — live i SpacetimeDB-laget. Brukes i rom, samarbeid, + samtaler. Streamer input og viser resultater i sanntid. +- **Flat versjon** — tradisjonelt mot PG. Brukes på bussen, alene, offline-aktig. + Fanger input og lagrer asynkront. + +Begge aksepterer alt: +- **Tekst** — skriving, Markdown, kodeblokker +- **Lyd** — voice memo, diktering → automatisk transkribert +- **Bilde** — foto, skjermbilde, tegning +- **AI-støtte** — spør AI, få forslag, la den transformere input +- **Nettoppslag** — lim inn URL, den berikes automatisk +- **Kommunikasjon** — samme primitiv for alene (dagbok), en-til-en (melding), + gruppe (kanal) + +Forskjellen mellom "jeg skriver dagbok", "jeg sender en melding" og "jeg lager +et kanban-kort" er ikke *hva brukeren gjør* — det er hvilke edges som knyttes +til resultatet. + +## Én tabell, edges definerer alt + +All output fra input-primitiven lander som noder i kunnskapsgrafen. Én tabell. +Ingen `messages`-tabell, ingen `cards`-tabell, ingen `notes`-tabell. + +Hva en node "er" bestemmes utelukkende av edges: +- Node + edge til kanal = chatmelding +- Node + edge til board + status-edge = kanban-kort +- Node + edge til dato = kalenderoppføring +- Node + edge til kun bruker (privat) = dagboknotis +- Node + edge til topic = faktoid i kunnskapsgrafen +- Node uten edges = løs tanke, ennå uorganisert + +**Retyping er trivielt.** Å gjøre en chatmelding om til et kanban-kort er å +legge til en edge til et board og en status-edge. Fjerne fra chat er å fjerne +kanal-edgen. Ingen datamigrering, ingen transformasjon av innhold. Bare edges. + +**Multitype er naturlig.** En node kan være *både* et kanban-kort *og* en +kalenderoppføring *og* en faktoid. Det er ikke en edge case — det er +arkitekturen. + +## Implikasjoner + +### Meldingsboksen erstattes av noe dypere +Meldingsboksen var riktig intuisjon — men den er en lagringsprimitiv som +prøver å forene ulike domenemodeller. Universell input + kunnskapsgrafen +gjør det renere: det finnes bare noder og edges. "Meldingsboks" blir et +view-konsept (hvordan noder vises i en kontekst), ikke et lagrings-konsept. + +### Input-metode og innholdstype er ortogonale +Du kan snakke inn et kanban-kort. Du kan tegne en kalenderoppføring. Du kan +skrive en voice memo (tekst som transkriberes til lyd for en annen bruker). +Input-primitiven bryr seg ikke om hva det *blir* — den fanger det som +kommer inn. + +### Samme input, ulik routing +Lyd inn i input-primitiven kan routes helt forskjellig basert på edges: +- Edge til et møterom → streames live til andre deltakere (sanntidslaget) +- Edge til kun deg selv → transkriberes og lagres som personlig notat +- Edge til en podcast-kanal → goes into produksjonspipeline +- Edge til en person → sendes som lydmelding + +Brukeren gjør det samme — snakker inn i input-feltet. *Systemet* router +basert på kontekst og edges. Det er ingen "møte-app" eller "notat-app" +eller "meldings-app" — det er én input med ulike destinasjoner. + +### Mottaker bestemmer format +All lyd transkriberes. All tekst kan leses opp (TTS). Noden har alltid +begge representasjoner. Mottaker setter sin preferanse: +- Trond snakker inn en tanke → node med lyd + transkripsjon +- Peter har tekst-preferanse → ser transkripsjonen +- Vegard har lyd-preferanse → hører originallyd +- Anna skriver tekst → node med tekst + TTS-versjon +- Trond har lyd-preferanse → hører TTS-opplesning av Annas tekst + +Senderen trenger ikke vite eller bry seg. Innholdet er det samme — +presentasjonen er en mottaker-side preferanse. Modalitet er ikke +en egenskap ved meldingen, men ved *lesningen* av den. + +### Én overflate å perfeksjonere +Brukerens mentale modell kollapser til én ting: input-feltet. All +UX-investering konsentreres ett sted i stedet for å smøres tynt utover +ti ulike grensesnitt. Én perfekt input-opplevelse — responsiv, multimodal, +med god AI-støtte — i stedet for ti middelmådige spesialgrensesnitt. + +Dette er en radikal forenkling av utviklingsoverflaten. I stedet for å +bygge og vedlikeholde chat-input, kanban-skjema, kalender-dialog, +notat-editor, voice-recorder, dagbok-felt — bygger vi *ett* grensesnitt +og investerer alt i å gjøre det feilfritt. Alt etterpå er edges. + +### Visninger er spørringer mot grafen +Chat-visningen = "vis noder med edge til denne kanalen, sortert på tid." +Kanban-visningen = "vis noder med edge til dette boardet, gruppert på status." +Kalender-visningen = "vis noder med dato-edge, plassert på tidslinje." +Dagbok-visningen = "vis private noder for denne brukeren, sortert på tid." + +Alle visninger leser fra samme graf. Ingen har "sin egen" data. + +### To versjoner passer to-lags-modellen +Sanntidsversjonen lever i SpacetimeDB-laget: input streames, resultater er +live, andre ser hva du gjør. Flat versjonen lever i det tradisjonelle laget: +input sendes, lagres i PG, ferdig. Begge produserer identiske noder i grafen. + +### Synlighet er bare en edge +Privat = edge kun til deg. Delt = edge til en gruppe/kanal. Publisert = edge +til en offentlig kontekst. Å "dele" noe er å legge til en edge. Å "gjøre +privat" er å fjerne den. Innholdet endres aldri. + +## Universelt mottak — den andre primitiven + +Input-primitiven er halvparten. Den andre halvdelen er *mottak*: hvordan +du konsumerer det andre produserer. Der input er "én overflate som fanger +alt", er mottak "én overflate som presenterer alt tilpasset *deg*." + +### Dimensjoner ved mottak + +**Format.** Lyd, tekst, visuelt — mottaker bestemmer (allerede beskrevet +over). Men det gjelder alt, ikke bare meldinger: en AI-oppsummering kan +leses eller høres. Et whiteboard-snapshot kan vises som bilde eller som +tekstlig beskrivelse. + +**Filtrering.** Hva ser du? Alt fra alle er støy. Mottaksflaten filtrerer +basert på dine edges: hvilke kanaler du følger, hvilke personer du +samarbeider med, hvilke topics du er interessert i. Du kuraterer ikke +manuelt — du justerer edges, og mottaksflaten oppdateres. + +**Prioritering.** Hva er viktig *nå*? En AI-assistert redaksjonell flate +som løfter frem det som trenger oppmerksomhet: ubesvarte meldinger, +oppgaver med frist, noder som er endret siden sist, tråder med aktivitet. +Ikke en notifikasjonsliste — en *vektet visning* av det som er relevant. + +**Tempo.** Sanntid eller asynkront. I sanntidslaget: ting streamer inn +mens de skjer — en kollega snakker, du hører/leser live. I det +tradisjonelle laget: du får en digest, en oppsummering, et overblikk +over hva som har skjedd siden sist. Samme noder, ulikt tempo. + +**Kilde.** Direkte fra en person, eller via en node. Et møte som +genererer innsikter. En AI-jobb som er ferdig. En tråd som har blitt +aktiv igjen. En podcast-episode som er klar for review. Kilden trenger +ikke være et menneske — det kan være en prosess, en hendelse, en +tilstandsendring i grafen. + +### Mottaksflaten som speilbilde av input + +| Input | Mottak | +|-------|--------| +| Én overflate for all input | Én overflate for alt mottak | +| Sender bestemmer ikke format | Mottaker bestemmer format | +| Modalitet er ortogonal | Presentasjon er ortogonal | +| Kontekst gir edges | Preferanser gir filtrering | +| Sanntid + flat versjon | Sanntid (stream) + asynkron (digest) | + +### Mange-til-én og mange-via-mange +Mottaksflaten håndterer naturlig: +- **Én-til-én** — Trond sender deg en melding +- **Mange-til-én** — fem personer i en kanal, du ser alt +- **Via node** — et møte genererer et referat, du mottar det +- **Via kjede** — en chatmelding → blir oppgave → oppgaven fullføres → + du får oppdatering. Hele kjeden er edges, og du ser resultatet i din + mottaksflate uten å ha fulgt hvert steg. + +### Mottaksflaten er også en visning av grafen +Akkurat som chat-visningen er "noder med kanal-edge sortert på tid", er +mottaksflaten "noder med edge til *meg*, vektet på relevans og tid." +Det er ikke en egen mekanisme — det er enda en spørring mot samme graf, +bare med *deg* som sentrum. + +## Kommunikasjonsnoden — den tredje primitiven + +Input fanger. Mottak presenterer. Men det mangler noe: *stedet* der folk +møtes. En kommunikasjonsnode er en node i grafen som samler deltakere, +definerer tilgangsregler, og fungerer som kontekst for input og mottak. + +### Én node, mange former + +En kommunikasjonsnode er konseptuelt identisk uansett skala: + +| Variant | Deltakere | Input-tilgang | Mottak-tilgang | +|---------|-----------|---------------|----------------| +| Én-til-én samtale | 2 | Begge | Begge | +| Gruppechat | N | Alle medlemmer | Alle medlemmer | +| Redaksjonsmøte | N | Alle medlemmer | Alle medlemmer | +| Allmøte | 1 + N | Lederen snakker | Alle lytter, noen kan rekke opp hånden | +| Podcastinnspilling | 2-4 + N | Vertene snakker | Alle lytter, markører for produsent | +| Livesending | 1-4 + ∞ | Vertene | Streamet til nettside/app, lyd eller video | +| Asynkron gjest | 1 + 1 | Gjest gir input innen frist | Redaksjonen mottar | + +Forskjellen er *ikke* ulike systemer — det er ulike edge-konfigurasjoner +på samme nodetype: +- **Eier-edge** — hvem kontrollerer noden (kan invitere, endre regler, avslutte) +- **Input-edge** — hvem kan gi input (snakke, skrive, tegne, dele skjerm) +- **Mottak-edge** — hvem kan motta (lytte, lese, se stream) +- **Rolle-edge** — spesialroller (moderator, produsent, gjest) + +### Kommunikasjonsnoden er en kontekst for de andre primitivene + +Når du gir input *i* en kommunikasjonsnode, arver inputen kontekst-edges +automatisk. Sier du noe i et møte → noden du skaper får edge til møtet. +Du trenger ikke tenke på det — konteksten følger med. + +Mottak i en kommunikasjonsnode er det samme som universelt mottak, bare +scoped til den noden: du ser/hører det andre deltakere gir som input, +presentert etter dine preferanser. + +### Livssyklus + +En kommunikasjonsnode kan være: +- **Live** — aktiv i sanntidslaget. Deltakere er til stede, input streames. +- **Asynkron** — aktiv i det tradisjonelle laget. Deltakere gir input i + eget tempo (chat, asynkron gjest). +- **Avsluttet** — arkivert i PG. Alt som ble sagt/delt er noder med edges + til kommunikasjonsnoden. Kan søkes, gjenfinnes, refereres. +- **Gjenåpnet** — løftet tilbake til sanntidslaget. "Vi tar opp tråden + fra forrige møte" er bokstavelig talt å reaktivere en node. + +### Skalering er en edge-endring, ikke en migrasjonsoperasjon + +En samtale mellom to blir et møte ved å legge til flere deltaker-edges. +Et møte blir en livesending ved å legge til offentlige mottak-edges. +En livesending blir en podcast ved å legge til publiserings-edges på +arkivert innhold. Ingen migrering, ingen konvertering — bare edges. + +## Tekniske forutsetninger + +### STT (tale → tekst): løst +Faster-whisper kjører lokalt, god norsk kvalitet. Allerede i stacken. + +### TTS (tekst → tale): løsbart +Norsk TTS lokalt er ikke godt nok ennå (Piper er "usable" men dårlig rytme, +XTTS-v2 støtter ikke norsk). Kommersielt er det løst: +- **ElevenLabs** — beste kvalitet, eksplisitt norsk med regionale aksenter +- **Azure Neural TTS** — god kvalitet, ~$15/M tegn +- **Google Cloud TTS** — god kvalitet, WaveNet/Neural2 + +Strategi: start med kommersiell API (ElevenLabs) bak AI Gateway, bytt til +lokal modell (Chatterbox Multilingual el.l.) når kvaliteten er god nok. +Brukeren merker ingenting — det er en backend-swap bak gatewayen. Samme +mønster som Whisper: tung jobb → jobbkø → worker → resultat som node-metadata. + +Følg med på: **Chatterbox Multilingual** (Resemble AI) — annonsert norsk +støtte, 350M params, lovende for lokal kjøring. + +## Spenninger og åpne spørsmål + +- **Ytelse.** Én tabell med *alt* i — skalerer det? PG med riktige indekser + og partisjonering håndterer mye, men det er en reell designbeslutning. +- **Skjema.** Noder trenger noe felles skjema (innhold, created_at, author). + Men ulike modaliteter har ulike metadata (transkripsjon, bildestørrelse, + varighet). Hva er felles og hva er edge-metadata? +- **AI-klassifisering.** Når brukeren bare "sier noe" — hvem bestemmer hvilke + edges som knyttes? Manuelt? AI-foreslått? Kontekstbasert (sa det i en + kanal → kanal-edge)? Sannsynligvis en blanding, men det krever gjennomtenkt + UX. +- **Migrering.** Meldingsboksen har allerede innhold. Kan vi migrere til + node+edge-modellen gradvis, eller er det et brudd? +- **Kompleksitet for utviklere.** "Alt er noder og edges" er konseptuelt rent, + men å bygge en kanban-visning som spørrer en graf er mer komplekst enn å + lese fra en `cards`-tabell. Er abstraksjonen verdt kompleksiteten? + +## Tre primitiver, én graf + +| Primitiv | Hva den gjør | Brukerens opplevelse | +|----------|-------------|---------------------| +| **Input** | Fanger alt — tekst, lyd, bilde, AI | Én overflate å snakke/skrive/tegne i | +| **Mottak** | Presenterer alt tilpasset deg | Én personlig flate med det som er relevant | +| **Kommunikasjon** | Samler folk med tilgangsregler | Et sted å være — samtale, møte, sending | + +Alt er noder og edges i samme graf. Input skaper noder. Mottak spør +grafen med deg som sentrum. Kommunikasjonsnoder gir kontekst og +tilgangsregler. Visninger (chat, kanban, kalender, dagbok, stream) +er bare spørringer med ulike filtre. + +## Forhold til andre retninger + +Denne retningen konkretiserer [rom, ikke forum](rom_ikke_forum.md): +- "Formløs input, struktur etterpå" → universell input + edges +- "To lag" → sanntidsversjon + flat versjon +- "Privat/delt som lag" → synlighet som edge +- "Siloer forsvinner" → alt er noder, visninger er spørringer +- "Rommet som primitiv" → kommunikasjonsnoden diff --git a/ops/README.md b/ops/README.md new file mode 100644 index 0000000..84ca53a --- /dev/null +++ b/ops/README.md @@ -0,0 +1,20 @@ +# Ops — Repeterbare vedlikeholdsjobber + +Denne mappen inneholder veldefinerte, repeterbare jobber for å holde prosjektet +ryddig og gjennomsiktig. Hver jobb er en markdown-fil med sjekkliste som kan +kjøres av Claude eller manuelt. + +## Jobber + +| Jobb | Fil | Frekvens | Beskrivelse | +|------|-----|----------|-------------| +| Ryddejobb | [ryddejobb.md](ryddejobb.md) | Annenhver uke / ved behov | Full revisjon av prosjektet — docs, kode, drift, fremdrift | +| Doc-audit | [doc-audit.md](doc-audit.md) | Månedlig / etter store endringer | Sjekk at docs/ stemmer med faktisk kode | +| Drift-sjekk | [drift-sjekk.md](drift-sjekk.md) | Ved deploy / ved behov | Asynkron tilstand mellom prod, lokal og docs | + +## Konvensjoner + +- Hver jobb har seksjonene: **Hva**, **Når**, **Sjekkliste**, **Sist kjørt** +- «Sist kjørt»-seksjonen oppdateres hver gang jobben kjøres +- Funn skrives som korte bullet points med dato +- Fikser gjøres underveis eller logges som oppgaver i `tasks/` diff --git a/ops/doc-audit.md b/ops/doc-audit.md new file mode 100644 index 0000000..bbe0f46 --- /dev/null +++ b/ops/doc-audit.md @@ -0,0 +1,46 @@ +# Doc-audit — Dokumentasjon vs kode + +## Hva +Målrettet gjennomgang av `docs/`-treet for å sikre at dokumentasjonen stemmer med +faktisk kode. Mer fokusert enn ryddejobben — kun docs. + +## Når +- Månedlig som rutine +- Etter store refaktoreringer eller arkitekturendringer +- Når nye docs legges til + +## Sjekkliste + +### 1. Filreferanser +- [ ] Sjekk alle filstier nevnt i docs — eksisterer de? +- [ ] Sjekk alle komponent-/modul-navn nevnt i docs — eksisterer de i koden? +- [ ] Sjekk alle route-referanser — matcher de `web/src/routes/`? + +### 2. CLAUDE.md doc-tre +- [ ] Er alle filer i `docs/` listet i CLAUDE.md doc-treet? +- [ ] Er det filer listet i CLAUDE.md som ikke eksisterer? +- [ ] Er beskrivelsene i doc-treet fortsatt dekkende? + +### 3. Tekniske detaljer +- [ ] Stemmer database-skjema beskrevet i docs med faktiske migrasjoner? +- [ ] Stemmer API-endepunkter beskrevet i docs med faktiske routes? +- [ ] Stemmer SpacetimeDB-tabeller/reducere i docs med modulen? + +### 4. Setup-docs +- [ ] `docs/setup/lokal.md` — fungerer stegene fortsatt? +- [ ] `docs/setup/produksjon.md` — stemmer med faktisk server-oppsett? +- [ ] `docs/setup/migration_safety.md` — er sjekklisten oppdatert? + +### 5. Proposals +- [ ] Er noen proposals implementert og bør flyttes til `concepts/` eller `features/`? +- [ ] Er noen proposals foreldet og bør slettes eller arkiveres? +- [ ] Er noen proposals egentlig retningsspørsmål og bør flyttes til `retninger/`? + +### 6. Retninger +- [ ] Er tesene i `docs/retninger/` fortsatt relevante? +- [ ] Har noen retninger modnet nok til å påvirke andre docs (arkitektur, features, infra)? +- [ ] Er det nye arkitektoniske spenninger som fortjener en egen retning? + +## Sist kjørt + +_Ikke kjørt ennå._ diff --git a/ops/drift-sjekk.md b/ops/drift-sjekk.md new file mode 100644 index 0000000..b7d21a6 --- /dev/null +++ b/ops/drift-sjekk.md @@ -0,0 +1,45 @@ +# Drift-sjekk — Prod vs lokal vs docs + +## Hva +Verifiser at produksjonsserveren, lokalt utviklermiljø og dokumentasjon er i synk. +Fanger opp tilfeller der noe er deployet men ikke dokumentert, eller dokumentert +men ikke implementert. + +## Når +- Før og etter deploy til produksjon +- Når noe oppfører seg annerledes i prod vs lokalt +- Ved mistanke om drift + +## Sjekkliste + +### 1. Git-status +- [ ] Er prod-server på siste commit? (`ssh sidelinja@157.180.81.26 'cd /srv/sidelinja/server && git log -1'`) +- [ ] Er det ucommittede endringer lokalt som burde vært pushet? +- [ ] Er det commits på Forgejo som ikke er deployet til prod? + +### 2. Database-migrasjoner +- [ ] Er alle lokale migrasjoner pushet til repo? +- [ ] Er alle migrasjoner i repo kjørt i prod? +- [ ] Stemmer migrasjonsnumre mellom miljøer? + +### 3. Docker-tjenester +- [ ] Kjører alle forventede containere i prod? (`docker compose ps`) +- [ ] Er det tjenester i `docker-compose.dev.yml` som mangler i prod (eller omvendt)? +- [ ] Er image-versjoner oppdatert? + +### 4. Miljøvariabler +- [ ] Er det nye env-vars lagt til lokalt som mangler i prod `.env`? +- [ ] Er det env-vars i prod som er utdaterte? +- [ ] Er secrets rotert der de bør være? + +### 5. SpacetimeDB-modul +- [ ] Er SpacetimeDB-modulen publisert med siste endringer? +- [ ] Stemmer modul-skjema mellom lokal og prod? + +### 6. Caddy / reverse proxy +- [ ] Er Caddyfile i repo synk med prod? +- [ ] Er det nye subdomener eller routes som mangler? + +## Sist kjørt + +_Ikke kjørt ennå._ diff --git a/ops/ryddejobb.md b/ops/ryddejobb.md new file mode 100644 index 0000000..8c807d0 --- /dev/null +++ b/ops/ryddejobb.md @@ -0,0 +1,65 @@ +# Ryddejobb — Full prosjektrevisjon + +## Hva +Systematisk gjennomgang av hele prosjektet for å oppdatere fremdrift, tette hull, +fjerne utdaterte referanser, og sikre at dokumentasjon stemmer med virkeligheten. + +## Når +- Annenhver uke som rutine +- Etter store implementeringsjobber +- Når prosjektet føles uoversiktlig + +## Sjekkliste + +### 1. CLAUDE.md — stemmer instruksjonene? +- [ ] Er stack-beskrivelsen oppdatert? +- [ ] Er doc-treet komplett (alle filer i `docs/` er listet)? +- [ ] Er reglene fortsatt relevante? +- [ ] Finnes det nye konvensjoner som bør inn? + +### 2. Docs vs virkelighet +- [ ] Gå gjennom `docs/concepts/` — stemmer beskrivelsene med hva som finnes i koden? +- [ ] Gå gjennom `docs/features/` — er det features beskrevet som ikke er påbegynt? Marker dem. +- [ ] Gå gjennom `docs/infra/` — stemmer infrastruktur-docs med `docker-compose.dev.yml` og prod? +- [ ] Gå gjennom `docs/setup/` — fungerer oppsettinstruksjonene fortsatt? +- [ ] Gå gjennom `docs/retninger/` — er tesene fortsatt relevante? Har noen modnet til beslutninger? +- [ ] Er det docs som refererer til filer, routes eller komponenter som ikke eksisterer? + +### 3. Kode-hygiene +- [ ] Ubrukte SvelteKit-routes (mapper i `web/src/routes/` uten innhold eller med stub) +- [ ] Ubrukte komponenter (filer i `web/src/lib/components/` som ikke importeres) +- [ ] Ubrukte Rust-moduler i worker +- [ ] Ubrukte SpacetimeDB-reducere eller tabeller +- [ ] Gamle migrations som bør dokumenteres eller konsolideres +- [ ] `package.json` / `Cargo.toml` — ubrukte dependencies + +### 4. Fremdriftsstatus +- [ ] Hva er faktisk implementert og fungerer? +- [ ] Hva er påbegynt men ufullstendig? +- [ ] Hva er kun planlagt (kun docs)? +- [ ] Oppdater en kort statusoversikt (kan legges i `ops/status.md` ved behov) + +### 5. Asynkron tilstand — prod vs lokal vs docs +- [ ] Stemmer `docker-compose.dev.yml` med det som faktisk kjøres lokalt? +- [ ] Er prod-server oppdatert med siste push? +- [ ] Er det migrasjoner som er kjørt lokalt men ikke i prod (eller omvendt)? +- [ ] Er miljøvariabler (.env) synkronisert mellom miljøer? + +### 6. CLAUDE.md minne +- [ ] Gå gjennom `~/.claude/projects/-home-vegard-server/memory/MEMORY.md` +- [ ] Fjern utdaterte minner +- [ ] Oppdater minner som har blitt unøyaktige +- [ ] Er det ny kunnskap fra nylige samtaler som bør lagres? + +### 7. Erfaringslogg +- [ ] Er det gjort arbeid nylig som mangler erfaringsdokumentasjon i `docs/erfaringer/`? +- [ ] Er eksisterende erfaringsdokumenter fortsatt relevante og korrekte? + +### 8. dev.sh og utviklermiljø +- [ ] Fungerer `./dev.sh` fra scratch? +- [ ] Er alle nødvendige tjenester dekket? +- [ ] Er det nye quirks eller workarounds som bør inn i scriptet? + +## Sist kjørt + +_Ikke kjørt ennå._