SpacetimeDB er nå helt fjernet fra Synops. Sanntid håndteres av PG LISTEN/NOTIFY + WebSocket i portvokteren (maskinrommet). Kode fjernet: - spacetimedb/ Rust-modul og spacetime.json - maskinrommet/src/stdb.rs (HTTP-klient for STDB-reducers) - frontend module_bindings/ (23 auto-genererte filer) - spacetimedb npm-avhengighet fra package.json - scripts/test-sanntid.sh (testet STDB-flyt) Infrastruktur: - Docker-container stoppet og fjernet fra docker-compose.yml - Caddy: fjernet /spacetime/* reverse proxy - maskinrommet-env.sh: fjernet STDB_IP og SPACETIMEDB_*-variabler - .env.example: fjernet SpacetimeDB-seksjoner Dokumentasjon oppdatert: - CLAUDE.md: stack, lagmodell, kjerneprinsipper, driftsmodell - docs/arkitektur.md: skrivestien, lesestien, datalag, teknologivalg - docs/retninger/datalaget.md: migrasjonshistorikk, status "fjernet" - 37 andre docs oppdatert (features, concepts, infra, ops, retninger) - Alle kode-kommentarer med STDB-referanser oppdatert Verifisert: maskinrommet bygger og starter OK, frontend bygger OK, helsesjekk returnerer 200. Caddy reloadet.
11 KiB
Feature: Universell overføring — flytt innhold mellom verktøy
Filsti: docs/features/universell_overfoering.md
1. Konsept
Universell overføring er kjerneinteraksjonen på arbeidsflaten: drag-and-drop mellom verktøy-paneler. Se arbeidsflaten for den overordnede retningen.
1.1 To moduser
| Modus | Hva skjer | Eksempel |
|---|---|---|
| Innholdstransfer | Ny node opprettes med source_material-edge til kilden |
Chatboble → artikkelverktøy: ny artikkelnode |
| Lettvekts-triage | Eksisterende node får ny edge/plassering | Chatboble → kanban: noden vises i kanban OG chat |
Default avhenger av verktøy-paret. Se kompatibilitetsmatrisen i arbeidsflaten. Bruker kan overstyre med modifier-tast (Shift = alltid ny node).
1.2 Grunnprinsipp
Bruker drar kort fra Chat → slipper på Artikkelverktøyet
→ Ny artikkelnode opprettes med sitert tekst
→ source_material-edge kobler artikkel → chatboble
→ Chatboblen forblir uendret i chatten
Bruker drar kort fra Chat → slipper på Kanban-brettet
→ Noden får board-edge + status-edge (lettvekts)
→ Noden vises i kanban OG chat (samme node, to kontekster)
Bruker kan alltid flytte (fjerne fra kilde) i stedet for å kopiere (beholde begge). Kontekstmeny gir valget.
2. Plasseringsrelasjon (Placement)
Når en melding vises i en kontekst (chat, kanban, storyboard, kalender), trenger vi metadata om hvordan den vises der. Dette er plasseringsrelasjonen — en edge i grafen mellom meldingen og konteksten, med metadata.
2.1 Datamodell
CREATE TABLE message_placements (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
message_id UUID NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,
context_type TEXT NOT NULL, -- 'chat', 'kanban', 'storyboard', 'calendar', 'notes'
context_id UUID NOT NULL, -- channel_id, board_id, episode_id, calendar_id, note_id
entered_at TIMESTAMPTZ NOT NULL DEFAULT now(), -- når objektet ankom denne konteksten
position JSONB, -- kontekst-spesifikk posisjon (se §2.2)
UNIQUE (message_id, context_type, context_id)
);
CREATE INDEX idx_placements_context ON message_placements(context_type, context_id, entered_at);
CREATE INDEX idx_placements_message ON message_placements(message_id);
2.2 Posisjonsdata per kontekst
position-feltet er JSONB og inneholder kontekst-spesifikk plassering:
| Kontekst | position-innhold | Eksempel |
|---|---|---|
| Chat | null (sorteres etter entered_at) |
null |
| Kanban | { "column_id": "...", "position": 1.5 } |
Kolonne + rekkefølge |
| Storyboard | { "x": 340, "y": 120 } |
Fritt canvas-posisjon |
| Kalender | { "date": "2026-03-20", "all_day": true } |
Dato + tidspunkt |
| Notes | { "position": 3 } |
Rekkefølge i notatet |
2.3 Forhold til eksisterende view-configs
message_placements erstatter ikke de eksisterende view-config-tabellene (kanban_card_view, calendar_event_view) umiddelbart. Strategien er:
- Fase 1:
message_placementsbrukes for nye kontekster (storyboard, notes) og for overføringsmekanikken - Fase 2: Eksisterende view-configs migreres gradvis til
message_placements(kanban-posisjon, kalender-dato) - Fase 3:
kanban_card_viewogcalendar_event_viewkan fjernes når all logikk bruker placements
2.4 entered_at vs created_at
messages.created_at= når meldingen ble skrevetmessage_placements.entered_at= når meldingen ankom denne konteksten
En melding opprettet mandag i chatten som dras til storyboardet onsdag har created_at = mandag og storyboard-plassering med entered_at = onsdag. I chatten sorteres den etter sin chat-plasserings entered_at — som er mandag (opprinnelig plassering). I storyboardet vises den på canvas-posisjonen, uavhengig av tid.
3. Sende-mekanikk (source)
3.1 Drag-and-drop
Brukeren drar et objekt ut av en verktøy-panel. Når objektet forlater verktøy-panel-grensen:
- Objektet blir en "ghost" (semi-transparent drag-representasjon)
- Andre verktøy-paneler som kan motta objektet highlighter sin mottakssone
- Slipp på en mottakssone → overføring
3.2 Kontekstmeny: "Send til..."
For situasjoner der drag-and-drop er upraktisk (mobil, lang avstand):
Høyreklikk kort → "Send til..." →
├── 📋 Kanban: Episodeplanlegging
├── 💬 Chat: #studio-diskusjon
├── 📅 Kalender: Redaksjonskalender
└── 🎬 Storyboard: Episode 47
Listen viser alle verktøy-paneler brukeren har tilgang til som kan motta meldinger.
3.3 Flytt vs. kopier
- Kopier (default): Meldingen får en ny plassering i mål-konteksten, beholder plasseringen i kilde-konteksten
- Flytt (hold Shift ved drag, eller velg i kontekstmeny): Plasseringen i kilde-konteksten fjernes, ny plassering opprettes i mål
4. Mottak-mekanikk (target)
Hver verktøy-panel-type definerer en mottaker som bestemmer hva som skjer når et objekt ankommer:
4.1 Mottaker per verktøy-panel-type
| Verktøy-panel | Default-plassering | Visuell feedback |
|---|---|---|
| Chat | Ny melding i bunnen, entered_at = now() |
Kort blinker inn i chatflyten |
| Kanban | Første kolonne (eller "Innboks"), posisjon øverst | Kort glir inn i kolonnen |
| Storyboard | Senter av viewport (eller slipp-posisjon) | Kort fader inn |
| Kalender | Dagens dato, heldagshendelse | Dato highlighter |
| Notes | Ny verktøy-panel i bunnen av notatet | Tekst fades inn |
4.2 Mottakssone-rendering og inkompatibilitet
Hver verktøy-panel rendrer en visuell drop-target når en drag er aktiv:
- Kompatibel: Panelet lyser opp med subtil border/glow
- Spesifikke soner (f.eks. en kolonne i kanban) highlighter ved hover
- Inkompatibel: Drop-sonen er rød/grå. Tooltip forklarer hvorfor og foreslår alternativ der det gir mening.
Se kompatibilitetsmatrisen i arbeidsflaten for komplett oversikt over hva som kan dras til hva.
4.3 Mottaker-interface
interface BlockReceiver {
/** Kan denne verktøy-panelen motta dette objektet? */
canReceive(payload: DragPayload): CompatResult;
/** Opprett plassering for mottatt objekt (optional — full impl i transfer service) */
receive?(payload: DragPayload, dropPosition?: { x: number; y: number }): PlacementIntent;
}
Alle verktøy-panel-typer implementerer canReceive(). receive() er optional —
den fulle overføringslogikken (ny node + edges) håndteres av transfer service (§ 1).
Drop-zone rendering (renderDropZone) håndteres av BlockShell, ikke
av trait-komponentene selv. BlockShell bruker receiver.canReceive() for å
bestemme visuell tilstand (compatible / incompatible), og viser overlay
med forklaring ved inkompatibilitet.
Kompatibilitetssjekker per verktøy-type finnes i $lib/transfer.ts:
checkChatCompat()— Chat aksepterer alt unntatt fra egen panelcheckKanbanCompat()— Kanban aksepterer kommunikasjon og innholdcheckCalendarCompat()— Kalender aksepterer kommunikasjon og innholdcheckEditorCompat()— Artikkelverktøy aksepterer tekst og mediacheckStudioCompat()— Studio aksepterer kun lydcheckAiToolCompat()— AI-verktøy aksepterer kun tekst
Factory-funksjon createBlockReceiver(toolType) oppretter en BlockReceiver
for en gitt verktøy-type.
5. Sanntidsintegrasjon
Plasseringsdata lagres i PG message_placements-tabellen. Endringer
propageres via LISTEN/NOTIFY + WebSocket for sanntidsoppdatering i frontend.
6. Responsivt design
- Desktop: Drag-and-drop mellom verktøy-paneler fungerer naturlig
- Tablet: Drag-and-drop fungerer med touch, men "Send til..."-meny er primær
- Mobil: Kun "Send til..."-meny (verktøy-paneler er stacked i én kolonne, drag mellom dem er upraktisk)
7. Bygger på
- Meldingsboks (
meldingsboks.md): Alle overførte objekter er meldingsbokser - Kunnskapsgraf (
kunnskapsgraf_og_relasjoner.md): Plasseringer er relasjoner i grafen - BlockShell / PageGrid: Verktøy-panel-rammen som rendrer mottakssoner
- PG LISTEN/NOTIFY + WebSocket: Sanntidssynk av plasseringer
8. Konsekvenser for eksisterende kode
8.1 BlockShell utvidelse
BlockShell trenger:
onDragEnter/onDragLeave/onDrophandlers for visuell feedback- Prop for
receiver: BlockReceiverfra innholdsverktøy-panelen - Fullskjerm-toggle (knapp i header + Esc for å lukke)
8.2 Ny plasserings-tabell
message_placements er ny. Eksisterende kanban_card_view og calendar_event_view lever parallelt inntil migrering.
8.3 Sanntid
PG NOTIFY-trigger på message_placements for sanntidspropagering via WebSocket.
9. Implementasjonsstatus
9.1 Transfer service ($lib/transfer.ts)
Sentral transferService med:
resolveTransferMode(source, target, shiftKey)— bestemmer modus fra verktøy-par. Bruker en matrise (DEFAULT_TRANSFER_MODE) som mapper source→target til default-modus. Shift-modifier overstyrer alltid tilinnholdstransfer.executeTransfer(accessToken, payload, intent, shiftKey)— utfører overføringen:- Innholdstransfer:
createNode()→createEdge(source_material)→createEdge(belongs_to) - Lettvekts-triage:
createEdge(belongs_to)med placement-metadata
- Innholdstransfer:
- Kompatibilitetssjekker per verktøy-type (
checkChatCompat,checkKanbanCompat, etc.) createBlockReceiver(toolType)— factory forBlockReceiver-implementasjoner
9.2 Shift-modifier
BlockShell.handleDropEvent()sendere.shiftKeytilonDrop-callback- Workspace
handlePanelDrop()sender shiftKey videre tilresolveTransferMode() - Shift = alltid ny node (innholdstransfer), uansett verktøy-par
9.3 Default-modus per verktøy-par
| Source → Target | Default-modus | Begrunnelse |
|---|---|---|
| chat → kanban | lettvekts-triage | Noden vises på brettet |
| chat → editor | innholdstransfer | Ny artikkelnode fra chatinnhold |
| chat → calendar | lettvekts-triage | Noden planlegges |
| kanban → calendar | lettvekts-triage | Oppgave planlegges |
| * → editor | innholdstransfer | Artikkelverktøyet skaper nytt innhold |
| * → ai_tool | innholdstransfer | AI skaper nytt innhold |
| * → chat/kanban/calendar/studio | lettvekts-triage | Noden vises i ny kontekst |
10. Instruks for Claude Code
- Innholdstransfer (ny node) brukes når innholdet transformeres (chat → artikkel)
- Lettvekts-triage (ny edge/plassering) brukes når kontekst legges til (chat → kanban)
source_material-edge kobler ny node tilbake til kilden ved innholdstransferentered_ater alltidnow()ved plassering, aldri kopiert fra kilden- En melding uten noen plasseringer er "løs" — UI advarer ved siste fjerning
- Inkompatible drops gir visuell feedback (rød/grå) + tooltip med forklaring
- Drag-and-drop bruker HTML5 API mellom paneler, pointer events intra-canvas
- Hold overføringslogikken i en sentral
transferService - Mottaker-interfacet er obligatorisk for alle verktøy-panel-typer
- Shift-modifier overstyrer alltid til innholdstransfer (ny node)