# 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](../retninger/arbeidsflaten.md) 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](../retninger/arbeidsflaten.md). 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 ```sql 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: 1. **Fase 1:** `message_placements` brukes for nye kontekster (storyboard, notes) og for overføringsmekanikken 2. **Fase 2:** Eksisterende view-configs migreres gradvis til `message_placements` (kanban-posisjon, kalender-dato) 3. **Fase 3:** `kanban_card_view` og `calendar_event_view` kan fjernes når all logikk bruker placements ### 2.4 `entered_at` vs `created_at` - `messages.created_at` = når meldingen ble skrevet - `message_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: 1. Objektet blir en "ghost" (semi-transparent drag-representasjon) 2. Andre verktøy-paneler som kan motta objektet highlighter sin mottakssone 3. 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](../retninger/arbeidsflaten.md) for komplett oversikt over hva som kan dras til hva. ### 4.3 Mottaker-interface ```typescript interface BlockReceiver { /** Kan denne verktøy-panelen motta dette objektet? */ canReceive(message: Message): boolean; /** Opprett plassering for mottatt objekt */ receive(message: Message, dropPosition?: { x: number, y: number }): Placement; /** Visuell feedback for aktiv drag */ renderDropZone(): void; } ``` Alle verktøy-panel-typer implementerer dette interfacet. ## 5. SpacetimeDB-integrasjon Plasseringsdata for sanntidskontekster (storyboard, kanban) eies av SpacetimeDB: ```rust #[table(name = message_placement, public)] pub struct MessagePlacement { #[primary_key] pub id: String, pub message_id: String, pub context_type: String, pub context_id: String, pub entered_at: Timestamp, pub position_json: String, // JSON-serialisert posisjon } #[reducer] pub fn place_message(ctx: &ReducerContext, placement: MessagePlacement) { ... } #[reducer] pub fn remove_placement(ctx: &ReducerContext, message_id: String, context_type: String, context_id: String) { ... } #[reducer] pub fn move_on_canvas(ctx: &ReducerContext, placement_id: String, new_position_json: String) { ... } ``` Sync-workeren persisterer til PG `message_placements`-tabellen. ## 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 - **SpacetimeDB** (`synkronisering.md`): Sanntidssynk av plasseringer ## 8. Konsekvenser for eksisterende kode ### 8.1 BlockShell utvidelse `BlockShell` trenger: - `onDragEnter`/`onDragLeave`/`onDrop` handlers for visuell feedback - Prop for `receiver: BlockReceiver` fra 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 SpacetimeDB-modul Ny tabell `message_placement` med reducers for place/remove/move. ## 9. 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 innholdstransfer - `entered_at` er alltid `now()` 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