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>
This commit is contained in:
parent
65b395082e
commit
7babafc65f
12 changed files with 909 additions and 1 deletions
|
|
@ -35,6 +35,8 @@ CLAUDE.md er eneste startdokument. Alt annet ligger under `docs/`:
|
|||
- `kanban.md` — Drag-and-drop planlegging
|
||||
- `kalender.md` — Redaksjonell kalender med abonnementsmodell og ICS-eksport
|
||||
- `notater.md` — Scratchpad/notatblokk med auto-save og debounce
|
||||
- `canvas_primitiv.md` — Felles fritt-canvas underlag (pan, zoom, drag — brukes av whiteboard og storyboard)
|
||||
- `universell_overfoering.md` — Flytt objekter mellom blokker (drag-and-drop + "Send til...")
|
||||
- `whiteboard.md` — Sanntids frihåndstavle (møterom, chat, solo)
|
||||
- `live_transkripsjon.md` — Whisper-pipeline (felles motor for studio/møter/fabrikk)
|
||||
- `live_ai.md` — Live AI: faktoid-oppslag (studio) + referent (møter)
|
||||
|
|
|
|||
213
docs/features/canvas_primitiv.md
Normal file
213
docs/features/canvas_primitiv.md
Normal file
|
|
@ -0,0 +1,213 @@
|
|||
# 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
|
||||
202
docs/features/universell_overfoering.md
Normal file
202
docs/features/universell_overfoering.md
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
# 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
|
||||
|
||||
```sql
|
||||
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:
|
||||
|
||||
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 blokk. Når objektet forlater blokk-grensen:
|
||||
|
||||
1. Objektet blir en "ghost" (semi-transparent drag-representasjon)
|
||||
2. Andre blokker 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 blokker i det aktive workspacet 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
|
||||
|
||||
```typescript
|
||||
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:
|
||||
|
||||
```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
|
||||
pub workspace_id: String,
|
||||
}
|
||||
|
||||
#[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`/`onDrop` handlers for visuell feedback
|
||||
- Prop for `receiver: BlockReceiver` fra 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_at` er alltid `now()` 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
|
||||
|
|
@ -39,10 +39,20 @@ Når en idé modnes nok til å bli implementert, skrives en full spec i `docs/fe
|
|||
| [Podcasting 2.0](podcasting_2_0.md) | Lav | Høy | Podcastfabrikken, kunnskapsgraf, RSS |
|
||||
| [Web Clipper](web_clipper.md) | Lav–Middels | Høy | Jobbkø, AI Gateway, meldingsboks, kunnskapsgraf |
|
||||
| [Visuelle Waveforms](waveforms.md) | Lav–Middels | Høy | Podcastfabrikken, jobbkø, editor |
|
||||
| **Innspilling & Storyboard** | | | |
|
||||
| [Storyboard](storyboard.md) | Middels–Stor | Høy | Canvas-primitiv, meldingsboks, universell overføring, Studioet, Podcastfabrikken |
|
||||
| [Card Chaining](card_chaining.md) | Lav | Middels | Kunnskapsgraf, Storyboard, AI Gateway |
|
||||
| [Ghost Cards](ghost_cards.md) | Lav–Middels | Høy | Storyboard, meldingsboks, kunnskapsgraf |
|
||||
| [Pinboard Mode](pinboard_mode.md) | Lav | Høy | Storyboard, kanban |
|
||||
| [Flow Meter](flow_meter.md) | Lav | Middels | Storyboard |
|
||||
| [Emotion Tags](emotion_tags.md) | Lav | Middels | Meldingsboks, kanban, storyboard |
|
||||
| **Samarbeid** | | | |
|
||||
| [Collaborative Cursors](collaborative_cursors.md) | Lav | Middels | SpacetimeDB, Svelte |
|
||||
| [Card Heat Map](card_heat_map.md) | Lav | Middels | Meldingsboks, kanban/storyboard |
|
||||
|
||||
**Forfremmet til feature:** [Meldingsboks](../features/meldingsboks.md) — universell diskusjonsprimitiv (erstatter separate modeller for chat, kanban-kort, kalenderhendelser, faktoider, notater).
|
||||
|
||||
**Lavthengende frukter** (lav innsats, høy wow): Serendipity Roulette, Podcast Time Machine, Meme Generator, Audience Voice Memo.
|
||||
**Lavthengende frukter** (lav innsats, høy wow): Serendipity Roulette, Podcast Time Machine, Meme Generator, Audience Voice Memo, Pinboard Mode, Ghost Cards.
|
||||
|
||||
## Format
|
||||
Forslagsfiler er lette — ingen streng mal. Minimum:
|
||||
|
|
|
|||
26
docs/proposals/card_chaining.md
Normal file
26
docs/proposals/card_chaining.md
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
# Card Chaining — Automatisk kobling av relaterte kort
|
||||
|
||||
## Idé
|
||||
|
||||
Når to kort plasseres ved siden av hverandre (i kanban, storyboard eller kalender), opprettes automatisk en graf-edge mellom dem. Systemet kan også foreslå overganger: "Og apropos drømmer..."
|
||||
|
||||
## Hvorfor interessant?
|
||||
|
||||
Podcast-segmenter henger ofte sammen tematisk, men koblingen er implisitt. Card chaining gjør den eksplisitt — uten manuelt arbeid. Gir bedre flyt under innspilling og bedre metadata for kunnskapsgrafen.
|
||||
|
||||
## Fungerer slik
|
||||
1. Dra kort A ved siden av kort B i storyboard
|
||||
2. System oppretter `graph_edge` med `relation_type: 'sequence'` og `origin: 'proximity'`
|
||||
3. Valgfritt: AI foreslår overgangssetning basert på begge kortenes innhold
|
||||
4. Ved eksport/arkivering: sekvensen bevares som episode-struktur
|
||||
|
||||
## Bygger på
|
||||
- Kunnskapsgraf (graph_edges)
|
||||
- Storyboard (proximity detection)
|
||||
- AI Gateway (overgangsforslag)
|
||||
|
||||
## Innsats
|
||||
Lav — graph_edges finnes, bare UI for proximity + auto-edge.
|
||||
|
||||
## Wow-faktor
|
||||
Middels — subtilt, men forbedrer metadata-kvaliteten dramatisk over tid.
|
||||
25
docs/proposals/card_heat_map.md
Normal file
25
docs/proposals/card_heat_map.md
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
# Card Heat Map — Visuell indikator for engasjement
|
||||
|
||||
## Idé
|
||||
|
||||
Kort på storyboard/kanban gløder basert på hvor mye oppmerksomhet de har fått: hover-tid, antall redigeringer, diskusjonstråd-lengde, og tid brukt i innspilling.
|
||||
|
||||
## Hvorfor interessant?
|
||||
|
||||
Hjelper med å se hva teamet faktisk er engasjert i — uten å lese alt. Under innspilling: "det kortet gløder mest, kanskje vi bør ta det først."
|
||||
|
||||
## Fungerer slik
|
||||
1. Klient tracker hover-tid per kort (lokal state)
|
||||
2. Server aggregerer: antall edits, tråd-lengde, reaksjoner
|
||||
3. Kombinert score → CSS-variabel (`--heat: 0.0–1.0`) → glow-effekt
|
||||
4. Valgfritt: "Hot topics"-filter som sorterer kort etter heat
|
||||
|
||||
## Bygger på
|
||||
- Meldingsboks (reaksjoner, tråd-lengde)
|
||||
- Kanban/Storyboard (visuell rendering)
|
||||
|
||||
## Innsats
|
||||
Lav — ren frontend-logikk med enkel server-aggregering.
|
||||
|
||||
## Wow-faktor
|
||||
Middels — subtilt men nyttig for redaksjonell prioritering.
|
||||
29
docs/proposals/collaborative_cursors.md
Normal file
29
docs/proposals/collaborative_cursors.md
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
# Collaborative Cursors — Sanntids-pekere for flerbrukermiljø
|
||||
|
||||
## Idé
|
||||
|
||||
Alle brukere som er på samme side ser hverandres musepekere som fargede prikker med navn. Fungerer på storyboard, kanban, whiteboard og kalender.
|
||||
|
||||
## Hvorfor interessant?
|
||||
|
||||
Gir "jamming together"-følelse under innspilling og planlegging. Produsent og host ser hverandre jobbe i sanntid uten å snakke om det.
|
||||
|
||||
## Fungerer slik
|
||||
1. Klient sender `{ user_id, x, y, page }` til SpacetimeDB ved musebevegelse (throttlet til ~10 Hz)
|
||||
2. Andre klienter renderer fargede SVG-sirkler med brukernavn
|
||||
3. Prikken fader ut etter 5 sekunder uten bevegelse
|
||||
4. Valgfritt: kort "trail" som viser bevegelsesretning
|
||||
|
||||
## Bygger på
|
||||
- SpacetimeDB (pub/sub for posisjoner)
|
||||
- Svelte ($state store for cursor-map)
|
||||
|
||||
## Innsats
|
||||
Lav — under 50 linjer Svelte + en SpacetimeDB-reducer.
|
||||
|
||||
## Wow-faktor
|
||||
Middels — visuelt tiltalende, men ikke kritisk funksjonalitet.
|
||||
|
||||
## Åpne spørsmål
|
||||
- Bør pekere vises i chat-visning også, eller bare canvas-baserte views?
|
||||
- Throttling-strategi: SpacetimeDB-reducer eller klient-side debounce?
|
||||
26
docs/proposals/emotion_tags.md
Normal file
26
docs/proposals/emotion_tags.md
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
# Emotion Tags — Hurtigkategorisering av segmenter
|
||||
|
||||
## Idé
|
||||
|
||||
Kort i storyboard/kanban kan tagges med stemnings-ikoner — morsom, seriøs, kontroversiell, personlig. Taggen farger kortets ramme og fungerer som filter i etterarbeid.
|
||||
|
||||
## Hvorfor interessant?
|
||||
|
||||
Under redigering er det nyttig å filtrere på stemning: "vis alle kontroversielle segmenter" eller "vi trenger en morsom bit mellom to tunge tema." Raskere enn å lese alle kortene.
|
||||
|
||||
## Fungerer slik
|
||||
1. Predefinerte tags med ikoner og farger (konfigurerbart per workspace)
|
||||
2. Klikk-basert tagging — ett klikk per tag, toggle on/off
|
||||
3. Visuelt: farget border + ikon-badge på kortet
|
||||
4. Filter: "vis kun 🔥-kort" i storyboard og kanban
|
||||
5. Lagres som `message.metadata.emotion_tags: string[]`
|
||||
|
||||
## Passer inn i eksisterende
|
||||
- **Meldingsboks**: tags i metadata-feltet, ingen ny tabell
|
||||
- **Reaksjoner**: kan gjenbruke reaksjons-mekanismen (emoji = emotion tag)
|
||||
|
||||
## Innsats
|
||||
Lav — ren frontend + metadata-felt.
|
||||
|
||||
## Wow-faktor
|
||||
Middels — nyttig for store episoder med mange segmenter.
|
||||
24
docs/proposals/flow_meter.md
Normal file
24
docs/proposals/flow_meter.md
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
# Flow Meter — Visuell episodeprogresjon
|
||||
|
||||
## Idé
|
||||
|
||||
En tynn progresjonslinje langs toppen av storyboardet som fylles etter hvert som kort dras til "Tatt opp". Grønn = solid episode, gul = trenger mer, rød = for kort. Basert på antall segmenter og total opptakstid.
|
||||
|
||||
## Hvorfor interessant?
|
||||
|
||||
Under innspilling er det lett å miste oversikten over om man har nok materiale. Flow meter gir et intuitivt "mage-sjekk" uten å telle manuelt.
|
||||
|
||||
## Fungerer slik
|
||||
1. Konfigurerbar målvarighet per workspace (f.eks. 45 min)
|
||||
2. Summerer varighet for alle "Tatt opp"-kort
|
||||
3. Fargeovergang: rød (0–30%) → gul (30–70%) → grønn (70–100%)
|
||||
4. Valgfritt: pulserer sakte når man nærmer seg mål
|
||||
|
||||
## Bygger på
|
||||
- Storyboard (episode-sekvens med tidsstempler)
|
||||
|
||||
## Innsats
|
||||
Lav — én beregning + CSS gradient.
|
||||
|
||||
## Wow-faktor
|
||||
Middels — liten ting, men fjerner mental overhead.
|
||||
27
docs/proposals/ghost_cards.md
Normal file
27
docs/proposals/ghost_cards.md
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
# Ghost Cards — Visuelle spor fra tidligere episoder
|
||||
|
||||
## Idé
|
||||
|
||||
Når en episode er ferdig og arkivert, etterlater kort som ble "Tatt opp" svake, semi-transparente spøkelseskort på storyboardet. De fungerer som påminnelser: "vi snakket om dette forrige uke — har vi oppfølging?"
|
||||
|
||||
## Hvorfor interessant?
|
||||
|
||||
Podcast-temaer henger sammen over tid. Ghost cards gir visuell kontinuitet mellom episoder uten manuell sporing. Perfekt for serier og løpende historier.
|
||||
|
||||
## Fungerer slik
|
||||
1. Ved arkivering: kort som var "Tatt opp" får `ghost_episode_id` i metadata
|
||||
2. Ved neste episode: ghost cards vises med `opacity: 0.3` og episode-nummer
|
||||
3. Klikk på ghost → se original diskusjonstråd og tidsstempel
|
||||
4. Dra ghost → promoter til nytt aktivt kort for oppfølging (beholder graf-edge til originalen)
|
||||
5. Konfigurerbart: vis ghosts fra siste N episoder (default: 3)
|
||||
|
||||
## Bygger på
|
||||
- Storyboard (visuell rendering)
|
||||
- Meldingsboks (message med view-config)
|
||||
- Kunnskapsgraf (edge mellom original og oppfølger)
|
||||
|
||||
## Innsats
|
||||
Lav–Middels — mest metadata-design og UI for ghost-rendering.
|
||||
|
||||
## Wow-faktor
|
||||
Høy — gir podcasten "hukommelse" som oppleves magisk for brukeren.
|
||||
26
docs/proposals/pinboard_mode.md
Normal file
26
docs/proposals/pinboard_mode.md
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
# Pinboard Mode — Fugleperspektiv over episode-arc
|
||||
|
||||
## Idé
|
||||
|
||||
Hurtigtast (f.eks. `Ctrl+0`) zoomer ut storyboardet til fugleperspektiv. Kort krymper, titler blir store. Du ser hele episodens arc som en visuell flyt: intro → morsom bit → dypt spørsmål → outro. Dra kort som Lego-klosser for å endre rekkefølge.
|
||||
|
||||
## Hvorfor interessant?
|
||||
|
||||
Under innspilling er man "for nær" — man ser enkelt-kort, ikke helheten. Pinboard mode gir 3-sekunders overblikk: "vi har tre morsomme segmenter på rad, vi trenger noe tungt i midten."
|
||||
|
||||
## Fungerer slik
|
||||
1. Toggle via hurtigtast eller knapp
|
||||
2. CSS transform: `scale(0.4)` + økt font-size på titler
|
||||
3. Kort viser kun: tittel, status-farge, varighet (hvis tatt opp)
|
||||
4. Drag-and-drop endrer rekkefølge i episode-sekvensen
|
||||
5. Klikk på kort = zoom tilbake til normalvisning på det kortet
|
||||
|
||||
## Bygger på
|
||||
- Storyboard (episode-sekvens)
|
||||
- Kanban (drag-and-drop)
|
||||
|
||||
## Innsats
|
||||
Lav — ren CSS/UI-jobb.
|
||||
|
||||
## Wow-faktor
|
||||
Høy — visuelt imponerende og genuint nyttig under innspilling.
|
||||
298
docs/proposals/storyboard.md
Normal file
298
docs/proposals/storyboard.md
Normal file
|
|
@ -0,0 +1,298 @@
|
|||
# Storyboard — Fritt canvas for innspillingsplanlegging og live-produksjon
|
||||
|
||||
## 1. Konsept
|
||||
|
||||
Storyboardet er et fritt canvas der meldingsboks-kort plasseres, flyttes og grupperes visuelt. Det brukes **før, under og etter innspilling** — fra idémyldring til ferdig episodestruktur. Ikke et erstatningsverktøy for kanban (som er for langsiktig planlegging), men et taktilt arbeidsflate for å *se* en episode ta form.
|
||||
|
||||
Storyboardet er en consumer av Canvas-primitivet (`docs/features/canvas_primitiv.md`) og deltar i universell overføring (`docs/features/universell_overfoering.md`).
|
||||
|
||||
## 2. Kjernemodell
|
||||
|
||||
### 2.1 Kort = Meldingsboks med storyboard-plassering
|
||||
|
||||
Alle kort på storyboardet er vanlige `messages`-noder. Plasseringen på canvaset styres av `message_placements`-tabellen:
|
||||
|
||||
```
|
||||
message_placements:
|
||||
message_id → messages.id
|
||||
context_type = 'storyboard'
|
||||
context_id → episode_id (eller standalone storyboard-id)
|
||||
entered_at = når kortet ble lagt på boardet
|
||||
position = { "x": 340, "y": 120, "status": "klar" }
|
||||
```
|
||||
|
||||
Kortet kan samtidig leve i en chat, på et kanban-brett, eller i en kalender. Endringer i chatten (nye svar, redigeringer) propagerer til storyboard-visningen fordi det er *samme melding*.
|
||||
|
||||
### 2.2 Episode-objekt
|
||||
|
||||
Et storyboard er knyttet til en episode (node av type `episode`) eller eksisterer som frittstående canvas (for idémyldring uten episode-tilknytning).
|
||||
|
||||
Episode-noden eier:
|
||||
- **Sekvensliste:** En ordnet liste over kort som er "Tatt opp", med tidsstempel for når de ble markert. Lagres som `episode_sequence` i SpacetimeDB.
|
||||
- **Målvarighet:** Konfigurerbar per workspace (f.eks. 45 min) — brukes av Flow Meter.
|
||||
|
||||
### 2.3 Statuser
|
||||
|
||||
Kort har status (lagret i `position`-JSONB på plasseringen):
|
||||
|
||||
| Status | Visuelt | Betydning |
|
||||
|--------|---------|-----------|
|
||||
| **Klar** | Solid, hvit/lys border | Planlagt, venter på tur |
|
||||
| **Tatt opp** | Grønn border, tidsstempel-badge | Snakket om under innspilling |
|
||||
| **Droppet** | Dimmet (opacity 0.4), rød stripe | Ikke brukt denne gang |
|
||||
| **Arkivert** | Skjult (filter) | Ferdig behandlet |
|
||||
|
||||
Status-endring skjer via:
|
||||
- Drag til en status-sone (valgfri — konfigurer per workspace)
|
||||
- Hurtigtast: `R` = Tatt opp, `D` = Droppet (når kort er valgt)
|
||||
- Kontekstmeny
|
||||
- Flytende toolbar ved seleksjon
|
||||
|
||||
### 2.4 Flere storyboards
|
||||
|
||||
Et workspace kan ha mange storyboards — ett per episode, pluss frittstående for idémyldring. Hver er en `StoryboardBlock` med `props.episodeId` (eller `props.boardId` for frittstående).
|
||||
|
||||
Tre episoder under planlegging = tre storyboard-blokker, enten:
|
||||
- På samme side (splittet grid-layout)
|
||||
- På ulike sider i workspace-navigasjonen
|
||||
|
||||
## 3. Fritt canvas
|
||||
|
||||
Storyboardet bruker Canvas-primitivet for all canvas-interaksjon:
|
||||
- **Pan/zoom:** Se `canvas_primitiv.md` §2
|
||||
- **Objekt-plassering:** Kort har `(x, y)` i world-space, ingen kolonner eller rader
|
||||
- **Snap-to-grid:** Av som default, toggle med `G`
|
||||
- **Ingen akse-begrensning:** Brukeren plasserer kort fritt. Ingen antatt retning eller tidslinje
|
||||
|
||||
### 3.1 Kort-rendering
|
||||
|
||||
Hvert kort rendres som en `<StoryboardCard>` inne i canvas-primitivets objekt-slot:
|
||||
|
||||
```
|
||||
┌─────────────────────────┐
|
||||
│ 🔵 Tittel │ ← Status-farge som border
|
||||
│ │
|
||||
│ Kort sammendrag av body │ ← Avkortet tekst
|
||||
│ │
|
||||
│ 💬 3 ⏱ 04:32 │ ← Svar-count + varighet (hvis tatt opp)
|
||||
└─────────────────────────┘
|
||||
```
|
||||
|
||||
Kort-størrelse er fast bredde (variabel ved zoom), høyde tilpasser seg innholdet opp til en max.
|
||||
|
||||
### 3.2 Kort-interaksjon
|
||||
|
||||
- **Klikk:** Velg kort, vis flytende toolbar
|
||||
- **Dobbeltklikk:** Åpne meldingsboksen i utvidet modus (full diskusjonstråd)
|
||||
- **Drag:** Flytt kort på canvaset
|
||||
- **Høyreklikk:** Kontekstmeny (status, send til, fjern, slett)
|
||||
|
||||
## 4. Overføring mellom blokker
|
||||
|
||||
Storyboardet deltar fullt i universell overføring (`universell_overfoering.md`):
|
||||
|
||||
### 4.1 Som sender
|
||||
|
||||
Dra et kort ut av storyboard-blokken → ghost følger musepekeren → slipp på annen blokk:
|
||||
- **→ Chat:** Melding ankommer som ny chatmelding med `entered_at = now()`
|
||||
- **→ Kanban:** Melding blir kort i første kolonne
|
||||
- **→ Kalender:** Melding blir hendelse på dagens dato
|
||||
- **→ Annet storyboard:** Melding får ny canvas-posisjon i mål-boardet
|
||||
|
||||
### 4.2 Som mottaker
|
||||
|
||||
Storyboardet kan motta fra alle blokk-typer:
|
||||
- **Default-plassering:** Senter av viewport
|
||||
- **Drag-plassering:** Der brukeren slipper objektet på canvaset
|
||||
- **Status:** Nye kort ankommer som "Klar"
|
||||
|
||||
### 4.3 Inter-storyboard overføring
|
||||
|
||||
For å flytte kort mellom episoder (f.eks. "dette passer bedre i episode 48"):
|
||||
- Dra kortet til en annen storyboard-blokk
|
||||
- Eller bruk "Send til..." → velg annet storyboard
|
||||
- Plasseringen i kilde-boardet fjernes, ny plassering opprettes i mål-boardet
|
||||
|
||||
## 5. Kort som oppstår under innspilling
|
||||
|
||||
Under live innspilling dukker nye idéer opp. Flere veier inn:
|
||||
|
||||
| Metode | Flyt |
|
||||
|--------|------|
|
||||
| **Hurtigtast (`N`)** | Popup for tittel + body → kort plasseres nær senter |
|
||||
| **Fra chat** | Skriv melding i studio-chat → "Send til Storyboard" |
|
||||
| **AI-forslag** | Live AI foreslår kort basert på transkripsjon (se §8) |
|
||||
|
||||
Alle veier oppretter en meldingsboks + plassering med status "Klar".
|
||||
|
||||
## 6. Kobling til LiveKit / Studioet
|
||||
|
||||
### 6.1 Tidsstempel ved "Tatt opp"
|
||||
|
||||
Når et kort settes til "Tatt opp" under en aktiv innspilling:
|
||||
|
||||
1. **Klient spør LiveKit** om nåværende innspillings-tidspunkt (offset fra oppstart)
|
||||
2. Tidspunktet lagres i plasserings-metadata: `position.recorded_at_offset = 1823` (sekunder)
|
||||
3. Episode-sekvensen oppdateres med kortet i riktig posisjon
|
||||
|
||||
Etter innspilling, når Whisper har prosessert lyden, kan `recorded_at_offset` matches mot Whisper-segmenter for å koble kort til eksakte transkripsjonsavsnitt.
|
||||
|
||||
### 6.2 Episode-sekvens
|
||||
|
||||
```rust
|
||||
#[table(name = episode_sequence_entry, public)]
|
||||
pub struct EpisodeSequenceEntry {
|
||||
#[primary_key]
|
||||
pub id: String,
|
||||
pub episode_id: String,
|
||||
pub message_id: String,
|
||||
pub sequence_position: f32, // REAL for midpoint-innsetting
|
||||
pub recorded_at_offset: Option<i64>, // sekunder fra innspillingsstart
|
||||
pub workspace_id: String,
|
||||
}
|
||||
```
|
||||
|
||||
Sekvensen er den ordnede listen over "Tatt opp"-kort og blir grunnlaget for episode-strukturen i Podcastfabrikken.
|
||||
|
||||
## 7. Etter innspilling
|
||||
|
||||
### 7.1 Episode-oppsummering
|
||||
|
||||
Automatisk generert visning etter innspilling:
|
||||
|
||||
```
|
||||
Episode 47 — Oppsummering
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
1. [00:00] Intro — Kommunevalg 2027 💬 2 svar
|
||||
2. [04:32] Listekandidatene i Oslo 💬 5 svar
|
||||
3. [12:15] Valgomaten — første resultater 💬 1 svar
|
||||
4. [18:40] Debatten om bompenger 💬 3 svar
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
Totalt: 23:10 av 45:00 mål (51%)
|
||||
Droppet: 3 kort (bevart for neste episode)
|
||||
```
|
||||
|
||||
### 7.2 Arkivering
|
||||
|
||||
Ett-klikks arkivering:
|
||||
1. Alle "Tatt opp"-kort settes til "Arkivert"
|
||||
2. "Droppet"-kort beholder status (synlige for neste episode som Ghost Cards)
|
||||
3. "Klar"-kort beholder status (ubrukte idéer)
|
||||
4. Episode-sekvensen fryses (immutable etter arkivering)
|
||||
|
||||
## 8. AI-integrasjon (fremtidig)
|
||||
|
||||
Under innspilling kan Live AI foreslå nye kort:
|
||||
|
||||
1. Whisper transkriberer i sanntid
|
||||
2. AI analyserer transkripsjon: "det ble nevnt et nytt tema som ikke er på boardet"
|
||||
3. AI oppretter et foreslått kort (status: "Foreslått", visuelt distinkt — stiplet border)
|
||||
4. Bruker aksepterer → status endres til "Klar"
|
||||
|
||||
Dette bygger på Live AI (`docs/features/live_ai.md`) og er ikke del av MVP.
|
||||
|
||||
## 9. SpacetimeDB-modell
|
||||
|
||||
### 9.1 Tabeller
|
||||
|
||||
```rust
|
||||
// Kort-posisjon og status (via universell overføring)
|
||||
// Bruker message_placement-tabellen — se universell_overfoering.md
|
||||
|
||||
// Episode-sekvens (ordnet liste over "Tatt opp"-kort)
|
||||
#[table(name = episode_sequence_entry, public)]
|
||||
pub struct EpisodeSequenceEntry {
|
||||
#[primary_key]
|
||||
pub id: String,
|
||||
pub episode_id: String,
|
||||
pub message_id: String,
|
||||
pub sequence_position: f32,
|
||||
pub recorded_at_offset: Option<i64>,
|
||||
pub workspace_id: String,
|
||||
}
|
||||
```
|
||||
|
||||
### 9.2 Reducers
|
||||
|
||||
```rust
|
||||
#[reducer]
|
||||
pub fn set_card_status(ctx: &ReducerContext, placement_id: String, status: String) { ... }
|
||||
|
||||
#[reducer]
|
||||
pub fn record_card(ctx: &ReducerContext, episode_id: String, message_id: String, offset: Option<i64>) {
|
||||
// Sett status til "Tatt opp" + legg til i episode-sekvens
|
||||
}
|
||||
|
||||
#[reducer]
|
||||
pub fn reorder_sequence(ctx: &ReducerContext, episode_id: String, entries: Vec<(String, f32)>) { ... }
|
||||
|
||||
#[reducer]
|
||||
pub fn archive_episode(ctx: &ReducerContext, episode_id: String) { ... }
|
||||
```
|
||||
|
||||
## 10. Responsivt design
|
||||
|
||||
| Skjerm | Tilpasning |
|
||||
|--------|-----------|
|
||||
| Desktop | Full canvas med zoom/pan, hurtigtaster, drag-and-drop |
|
||||
| Tablet | Touch-gester, flytende toolbar i bunn, "Send til"-meny |
|
||||
| Mobil | Listevisning av kort (gruppert etter status), "Send til"-meny, ingen canvas |
|
||||
|
||||
Mobil får en **alternativ listevisning** fordi fritt canvas på liten skjerm er upraktisk. Listen grupperer kort etter status og lar brukeren endre status via swipe.
|
||||
|
||||
## 11. Bygger på
|
||||
|
||||
| Feature / Infra | Rolle |
|
||||
|-----------------|-------|
|
||||
| Canvas-primitiv | Fritt canvas med zoom/pan/drag |
|
||||
| Meldingsboks | Kort = meldinger med view-config |
|
||||
| Universell overføring | Portal-mekanikk mellom blokker |
|
||||
| Kunnskapsgraf | Kort er noder, relasjoner mellom kort |
|
||||
| SpacetimeDB | Sanntidssynk av posisjon og status |
|
||||
| Studioet / LiveKit | Tidsstempel ved "Tatt opp" under innspilling |
|
||||
| Podcastfabrikken | Episode-sekvens → redigeringsgrunnlag |
|
||||
|
||||
## 12. Innsats
|
||||
Middels–Stor — canvas-primitivet er det tyngste løftet, men gjenbrukes av whiteboard. Selve storyboard-logikken er middels (status, sekvens, arkivering).
|
||||
|
||||
## 13. Wow-faktor
|
||||
Høy — dette er "limen" mellom redaksjonelt arbeid og faktisk innspilling. Visuelt imponerende og genuint nyttig.
|
||||
|
||||
## 14. Implementeringsfaser
|
||||
|
||||
### Fase 1: Fundament
|
||||
1. Canvas-primitiv (`<Canvas>`) med pan, zoom, drag, viewport culling
|
||||
2. BlockShell fullskjerm-toggle
|
||||
3. `message_placements`-tabell (PG + SpacetimeDB)
|
||||
4. `StoryboardBlock` registrert i block registry
|
||||
|
||||
### Fase 2: Kjerne-storyboard
|
||||
5. `<StoryboardCard>` med status-visning og hurtigtaster
|
||||
6. Episode-sekvens med SpacetimeDB-synk
|
||||
7. Universell overføring: "Send til..." kontekstmeny
|
||||
8. Drag-and-drop mellom blokker
|
||||
|
||||
### Fase 3: Innspillingsintegrasjon
|
||||
9. LiveKit-kobling for tidsstempler ved "Tatt opp"
|
||||
10. Episode-oppsummering og arkivering
|
||||
11. Kort opprettet under innspilling (hurtigtast + fra chat)
|
||||
|
||||
### Fase 4: Polish og utvidelser
|
||||
12. Ghost Cards (forrige episodes kort)
|
||||
13. Pinboard Mode (zoom-ut til fugleperspektiv)
|
||||
14. Flow Meter (visuell progresjon)
|
||||
15. Mobil listevisning
|
||||
16. AI-foreslåtte kort under innspilling
|
||||
|
||||
## 15. Åpne spørsmål (parkert)
|
||||
- Skal frittstående storyboards (uten episode) ha en egen node-type, eller er de bare episoder uten publiseringsdato?
|
||||
- Bør canvaset ha et bakgrunnsmønster (dots/grid) for orientering, eller blank?
|
||||
- Hvor mange kort tåler canvaset før ytelsen lider? Sannsynlig grense: 200–500 DOM-noder med viewport culling.
|
||||
|
||||
## 16. Instruks for Claude Code
|
||||
- Storyboard er en blokk-type (`StoryboardBlock.svelte`) i block registry
|
||||
- Kort-posisjon eies av SpacetimeDB via `message_placements` — aldri direkte PG fra frontend
|
||||
- Episode-sekvens er en egen SpacetimeDB-tabell, ikke metadata på episode-noden
|
||||
- Bruk Canvas-primitivet for all canvas-logikk — ikke reimplementer pan/zoom
|
||||
- Mobil-fallback (listevisning) er en egen komponent, ikke en "responsiv" versjon av canvaset
|
||||
- Status-endring skal alltid gå via SpacetimeDB reducer, aldri direkte state-mutasjon
|
||||
Loading…
Add table
Reference in a new issue