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>
213 lines
7.6 KiB
Markdown
213 lines
7.6 KiB
Markdown
# 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 50–200 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 (768–1024px) | 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 gå i fullskjerm. Dette er en generell feature, ikke spesifikk for canvas:
|
||
|
||
- **Toggle:** Dobbeltklikk på 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 å gå 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 på 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
|