server/docs/features/canvas_primitiv.md
vegard 7babafc65f Storyboard-spec, canvas-primitiv og universell overføring
Tre nye/omskrevne dokumenter som definerer fritt-canvas arkitekturen:
- Canvas-primitiv: felles underlag for whiteboard og storyboard (pan, zoom, drag, viewport culling)
- Universell overføring: message_placements-tabell og blokk-til-blokk drag-and-drop
- Storyboard: full spec med episode-sekvens, LiveKit-kobling, inter-board overføring

Inkluderer også storyboard-relaterte mini-proposals (ghost cards, pinboard mode,
flow meter, emotion tags, card chaining, collaborative cursors, card heat map).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-16 18:10:31 +01:00

213 lines
7.6 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Feature: Canvas-primitiv — felles fritt-canvas underlag
**Filsti:** `docs/features/canvas_primitiv.md`
## 1. Konsept
Canvas-primitivet er den felles underliggende komponenten for alle friform-views i Sidelinja: whiteboard (tegning), storyboard (kort-canvas), og fremtidige canvas-baserte visninger. Det håndterer kamera (pan, zoom), viewport-styring, objekt-plassering og interaksjon — men vet ingenting om *hva* som rendres.
### 1.1 Hvorfor et felles primitiv?
Whiteboard og storyboard har identisk infrastruktur-behov:
- Uendelig canvas med pan og zoom
- Objekter med `(x, y)`-posisjon
- Drag-and-drop av objekter
- Viewport culling (ikke render det som er utenfor synsfeltet)
- Touch-støtte (pinch-zoom, to-finger-pan)
- Responsivt design (fungerer på mobil, tablet, desktop)
Forskjellen er *innholdet*: whiteboard rendrer streker/figurer, storyboard rendrer meldingsboks-kort. Primitivet abstraherer det felles, slik at begge views gjenbruker 100 % av canvas-logikken.
### 1.2 Arkitekturprinsipp
```
Canvas-primitiv (felles)
├── Kamera: pan, zoom, transform matrix
├── Viewport: culling, synlige objekter
├── Interaksjon: pointer events, touch, drag
├── Grid: valgfri snap, hjelpelinje
└── Render-delegering: slot/callback for innhold
Whiteboard (consumer)
├── Tegneverktøy: penn, linje, rektangel, tekst
├── Strøk-modell: SVG paths / canvas paths
└── SpacetimeDB: strøk-synkronisering
Storyboard (consumer)
├── Kort-rendering: <MessageBox> i kompakt modus
├── Status-modell: Klar / Tatt opp / Droppet / Arkivert
├── Portal-soner: overføringsmekanikk til andre blokker
└── SpacetimeDB: kort-posisjon + status-synkronisering
```
## 2. Kamera-modell
### 2.1 Transform
Kameraet representeres som en 2D affin transformasjon:
```typescript
interface Camera {
x: number; // pan offset X (world coords)
y: number; // pan offset Y (world coords)
zoom: number; // scale factor (1.0 = 100%)
}
```
Rendring via CSS `transform` på en wrapper-div:
```css
.canvas-world {
transform: translate(calc(var(--cam-x) * 1px), calc(var(--cam-y) * 1px))
scale(var(--cam-zoom));
transform-origin: 0 0;
}
```
### 2.2 Zoom-begrensning
- Min zoom: `0.1` (10 % — fugleperspektiv, brukes av Pinboard Mode)
- Max zoom: `3.0` (300 % — detalj)
- Default: `1.0`
- Zoom pivoterer rundt musepeker/finger-midtpunkt
### 2.3 Pan
- **Desktop:** Hold mellomknapp eller mellomrom + dra. Alternativt: to-finger-drag på trackpad.
- **Touch:** To-finger-pan (én finger = dra objekter, to fingre = pan).
- **Edge-pan:** Når man drar et objekt nær kanten av viewport, scroller canvaset automatisk i den retningen.
## 3. Viewport Culling
Bare objekter som overlapper med det synlige viewport-rektangelet rendres i DOM. For storyboard med 50200 kort er dette en optimalisering som holder DOM-et lett.
```typescript
function visibleObjects(objects: CanvasObject[], camera: Camera, viewportSize: { w: number, h: number }): CanvasObject[] {
const worldRect = screenToWorld(camera, viewportSize);
return objects.filter(obj => intersects(obj.bounds, worldRect));
}
```
En margin (f.eks. 200px i world-space) legges til for å unngå pop-in ved pan.
## 4. Objektmodell
Canvas-primitivet opererer på generiske objekter:
```typescript
interface CanvasObject {
id: string;
x: number;
y: number;
width: number;
height: number;
// Consumer-spesifikk data håndteres via generics/props
}
```
Consumer (whiteboard, storyboard) bestemmer *hva* som rendres for hvert objekt via en render-callback eller Svelte snippet:
```svelte
<Canvas objects={cards} let:object>
<!-- Consumer bestemmer innholdet -->
<StoryboardCard card={object} />
</Canvas>
```
## 5. Interaksjon
### 5.1 Pointer events
All interaksjon håndteres via pointer events (unified mouse + touch):
| Gest | Desktop | Touch | Handling |
|------|---------|-------|----------|
| Pan | Mellomknapp-drag / Space+drag | To-finger-drag | Flytt kamera |
| Zoom | Scroll wheel | Pinch | Zoom inn/ut |
| Velg | Klikk | Tap | Velg objekt |
| Flytt | Venstreklikk-drag på objekt | Én-finger-drag på objekt | Flytt objekt |
| Multi-select | Shift+klikk / lasso | Lang-trykk + drag | Velg flere |
### 5.2 Snap-to-grid (valgfri)
Når aktivert, snapper objekter til et rutenett ved drag-slipp:
```typescript
function snap(value: number, gridSize: number): number {
return Math.round(value / gridSize) * gridSize;
}
```
Default: av. Kan toggles via hurtigtast eller toolbar.
### 5.3 Seleksjon
- Klikk på tom flate: deselect alle
- Klikk på objekt: velg det (deselect andre)
- Shift+klikk: toggle seleksjon
- Lasso: dra på tom flate uten Space = tegn seleksjonsboks
## 6. Responsivt design
Canvas-primitivet skal fungere på alle skjermstørrelser:
| Skjerm | Tilpasning |
|--------|-----------|
| Desktop (>1024px) | Full interaksjon, alle hurtigtaster |
| Tablet (7681024px) | Touch-gester, toolbar i bunn |
| Mobil (<768px) | Forenklet toolbar, større treffområder for objekter, ingen lasso |
Touch-treffområder skal være minimum 44x44px (WCAG 2.5.5).
## 7. Fullskjerm-modus (BlockShell-feature)
Enhver blokk i `BlockShell` kan i fullskjerm. Dette er en generell feature, ikke spesifikk for canvas:
- **Toggle:** Dobbeltklikk blokk-headeren, eller knapp i header
- **Implementering:** Blokken settes til `position: fixed; inset: 0; z-index: 50`
- **Escape:** Trykk Esc eller klikk "minimer"-knapp for å tilbake
- **URL-state:** Fullskjerm-tilstand lagres ikke i URL det er en visuell modus, ikke en side
## 8. SpacetimeDB-integrasjon
Canvas-primitivet selv har ingen SpacetimeDB-kobling det er consumer-ens ansvar. Men primitivet eksponerer events som consumeren kan koble til SpacetimeDB:
```typescript
interface CanvasEvents {
onObjectMove: (id: string, x: number, y: number) => void;
onObjectResize: (id: string, w: number, h: number) => void;
onCameraChange: (camera: Camera) => void;
onSelectionChange: (ids: string[]) => void;
}
```
Storyboard-consumeren bruker `onObjectMove` til å kalle en SpacetimeDB-reducer for å synkronisere posisjon til andre klienter.
## 9. Bygger på
- **SvelteKit:** Svelte 5 `$state`/`$derived` for reaktiv kamera- og objekt-state
- **CSS transforms:** Ingen Canvas2D eller WebGL DOM-basert rendering for å beholde Svelte-komponent-rendering inne i objektene
- **Pointer Events API:** Unified input for mus og touch
## 10. Implementeringsstrategi
### Fase 1: Kjerne-primitiv
- `<Canvas>` Svelte-komponent med kamera (pan/zoom), viewport culling, og objekt-drag
- Touch-støtte (pinch-zoom, to-finger-pan)
- BlockShell fullskjerm-toggle
### Fase 2: Storyboard som første consumer
- `<StoryboardCard>` rendrer meldingsboks-kort canvaset
- SpacetimeDB-synk for posisjon og status
- Portal-soner for overføring
### Fase 3: Whiteboard-migrering
- Migrere eksisterende whiteboard-spec til å bruke canvas-primitivet
- Tegneverktøy som overlay oppå primitivet
## 11. Instruks for Claude Code
- Canvas-primitivet er en ren Svelte-komponent uten backend-avhengigheter
- Bruk CSS transforms, ikke Canvas2D innholdet inne i objekter er vanlige Svelte-komponenter
- All state styres via Svelte 5 `$state` og `$derived` ingen external state management
- Pointer events, ikke mouse events unified input
- Test med touch-emulering i DevTools for responsivitet
- Viewport culling er påkrevd fra dag 1 ikke optimaliser bort