Grunnleggende arkitekturbeslutninger tatt og dokumentert: - Alt er noder (brukere, team, innhold, mediefiler, samlings-noder) - Edges definerer hva en node er (freeform typer, metadata i JSONB) - Materialisert tilgangsmatrise (node_access) erstatter workspace-RLS - Visibility (hidden/discoverable/readable/open) på noder - Aliaser via usynlige system-edges - Maskinrommet eier all skriving (SpacetimeDB først, PG asynk) - SpacetimeDB holder hele grafen, PG er persistent backup - Node- og edge-skjema spesifisert (docs/primitiver/) Fjernet workspace-konseptet fra hele dokumentasjonen (~40 filer). Fem retninger besluttet, én åpen (rom, ikke forum). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
8.6 KiB
Feature: Universell overføring — flytt objekter mellom blokker
Filsti: docs/features/universell_overfoering.md
1. Konsept
Universell overføring er mekanikken som lar brukere flytte meldingsboks-objekter mellom vilkårlige blokker: fra storyboard til chat, fra kanban til kalender, fra chat til storyboard. Enhver blokk kan sende og motta objekter. Meldingsboksen er alltid det underliggende objektet — overføringen endrer kun view-config, ikke selve meldingen.
1.1 Grunnprinsipp
En meldingsboks (node i kunnskapsgrafen) kan ha flere samtidige roller via view-configs (se meldingsboks.md §4). Universell overføring gjør dette til en førsteklasses brukerinteraksjon:
Bruker drar kort fra Storyboard → slipper på Chat-blokken
→ Meldingen får en ny plassering i chatten (placement-record)
→ Meldingen beholder sin posisjon på storyboardet
→ Diskusjonstråden er synlig begge steder (samme objekt)
Alternativt kan brukeren flytte (fjerne fra kilde, legge til i mål) 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 messages(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 blokk. Når objektet forlater blokk-grensen:
- Objektet blir en "ghost" (semi-transparent drag-representasjon)
- Andre blokker 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 blokker 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 blokk-type definerer en mottaker som bestemmer hva som skjer når et objekt ankommer:
4.1 Mottaker per blokk-type
| Blokk | 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 blokk i bunnen av notatet | Tekst fades inn |
4.2 Mottakssone-rendering
Hver blokk rendrer en visuell drop-target når en drag er aktiv:
- Hele blokken lyser opp med en subtil border/glow
- Spesifikke soner (f.eks. en kolonne i kanban, en dato i kalender) highlighter ved hover
- Avviste drops (feil type objekt) viser en dempet tilstand
4.3 Mottaker-interface
interface BlockReceiver {
/** Kan denne blokken 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 blokk-typer implementerer dette interfacet.
5. SpacetimeDB-integrasjon
Plasseringsdata for sanntidskontekster (storyboard, kanban) eies av SpacetimeDB:
#[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 blokker fungerer naturlig
- Tablet: Drag-and-drop fungerer med touch, men "Send til..."-meny er primær
- Mobil: Kun "Send til..."-meny (blokker 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: Blokk-rammen som rendrer mottakssoner
- SpacetimeDB (
synkronisering.md): Sanntidssynk av plasseringer
8. Konsekvenser for eksisterende kode
8.1 BlockShell utvidelse
BlockShell trenger:
onDragEnter/onDragLeave/onDrophandlers for visuell feedback- Prop for
receiver: BlockReceiverfra innholdsblokken - 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
- Overføring oppretter aldri en kopi av meldingen — kun en ny plassering (view-config)
entered_ater alltidnow()ved overføring, aldri kopiert fra kilden- En melding uten noen plasseringer er "løs" — den eksisterer i grafen men vises ikke noe sted. UI skal advare om dette ved siste fjerning
- Drag-and-drop bruker HTML5 Drag and Drop API for blokk-til-blokk, og pointer events for intra-canvas (storyboard/whiteboard)
- Hold overføringslogikken i en sentral
transferService— ikke spread ut i hver blokk-type - Mottaker-interfacet er obligatorisk for alle blokk-typer