All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 7s
- Add brainstorm feature with collaborative canvas, AI idea generation, live cursors, playback, and export - Add PDF upload/extraction/ingestion pipeline with pgvector document search (RAG) - Add document Q&A overlay with streaming chat and PDF preview - Add note attachments UI with status polling, grid layout, and auto-scroll - Add task extraction AI tool and agent executor improvements - Fix NoteEmbedding missing updatedAt column, re-index 66 notes with 1536-dim embeddings - Fix brainstorm 'Create Note' button: add success toast and redirect to created note - Fix memory echo notification infinite polling - Fix chat route to always include document_search tool - Add brainstorm i18n keys across all 14 locales - Add socket server for real-time brainstorm collaboration - Add hierarchical notebook selector and organize notebook dialog improvements - Add sidebar brainstorm section with session management - Update prisma schema with brainstorm tables, attachments, and document chunks
535 lines
25 KiB
Markdown
535 lines
25 KiB
Markdown
# Brainstorm — Documentation Complète
|
||
|
||
## Vue d'ensemble
|
||
|
||
Le brainstorm est un outil de génération d'idées assistée par IA basé sur un graphe radial D3. L'utilisateur saisit une "graine" (seed idea), l'IA génère 9 idées en 3 vagues (Variations, Analogies, Disruptions), et l'utilisateur peut approfondir, dismiss, convertir en note, ou ajouter ses propres idées. Le tout en temps-réel via Socket.io.
|
||
|
||
---
|
||
|
||
## Architecture
|
||
|
||
```
|
||
[Browser] ←→ [Next.js API Routes] ←→ [PostgreSQL]
|
||
↕ ↕
|
||
[Socket.io Client] ←→ [Socket.io Server :3002]
|
||
```
|
||
|
||
- **Frontend** : React + D3.js + React Query + Socket.io Client
|
||
- **Backend** : Next.js App Router + Prisma + OpenAI
|
||
- **Temps réel** : Socket.io sur le port 3002
|
||
|
||
---
|
||
|
||
## Modèles de données (Prisma)
|
||
|
||
### BrainstormSession
|
||
| Champ | Type | Description |
|
||
|-------|------|-------------|
|
||
| id | String (cuid) | PK |
|
||
| seedIdea | String | L'idée graine saisie par l'utilisateur |
|
||
| sourceNoteId | String? | FK vers la note source (optionnel) |
|
||
| contextNoteIds | String? | JSON string des IDs des notes de contexte |
|
||
| exportedNoteId | String? | FK vers la note exportée |
|
||
| userId | String | FK vers le propriétaire |
|
||
| inviteToken | String? | Token d'invitation unique |
|
||
| inviteExpiry | DateTime? | Date d'expiration du token |
|
||
| status | String | "active" par défaut |
|
||
| Relations | | ideas[], participants[], activities[], shares[] |
|
||
|
||
### BrainstormIdea
|
||
| Champ | Type | Description |
|
||
|-------|------|-------------|
|
||
| id | String (cuid) | PK |
|
||
| sessionId | String | FK vers la session |
|
||
| waveNumber | Int | 1, 2 ou 3 |
|
||
| title | String | Titre de l'idée |
|
||
| description | String | Description détaillée |
|
||
| connectionToSeed | String? | Lien avec l'idée graine |
|
||
| noveltyScore | Int? | Score de nouveauté (0-100) |
|
||
| parentIdeaId | String? | FK vers l'idée parent (auto-référence) |
|
||
| convertedToNoteId | String? | FK vers la note créée par conversion |
|
||
| relatedNoteIds | String? | JSON string des IDs des notes liées |
|
||
| status | String | "active", "dismissed", "converted" |
|
||
| positionX | Float? | Position X sur le canvas |
|
||
| positionY | Float? | Position Y sur le canvas |
|
||
| createdBy | String? | FK vers l'utilisateur créateur |
|
||
| createdByType | String | "ai" ou "human" |
|
||
| Relations | | session, parentIdea?, children[], noteRefs[], creator? |
|
||
|
||
### BrainstormNoteRef
|
||
| Champ | Type | Description |
|
||
|-------|------|-------------|
|
||
| id | String (cuid) | PK |
|
||
| ideaId | String | FK vers l'idée |
|
||
| noteId | String? | FK vers la note (peut être null) |
|
||
| relation | String | "derived_from", "opposes", "extends", "synthesizes", "transposes", "none_found" |
|
||
| explanation | String | Pourquoi cette note est liée |
|
||
| verdict | String | "unresolved", "accepted", "dismissed" |
|
||
|
||
### BrainstormParticipant
|
||
| Champ | Type | Description |
|
||
|-------|------|-------------|
|
||
| sessionId + userId | Unique | Un user = un rôle par session |
|
||
| role | String | "host", "editor", "viewer" |
|
||
| joinedAt, lastSeenAt | DateTime | Présence |
|
||
|
||
### BrainstormActivity
|
||
| Champ | Type | Description |
|
||
|-------|------|-------------|
|
||
| action | String | "wave_generated", "manual_idea", "idea_dismissed", "idea_converted", "joined", "invite_created" |
|
||
| details | String? | JSON stringifié |
|
||
|
||
### BrainstormShare
|
||
| Champ | Type | Description |
|
||
|-------|------|-------------|
|
||
| sessionId + userId | Unique | Un partage par couple session/user |
|
||
| sharedBy | String | FK vers l'envoyeur |
|
||
| status | String | "pending", "accepted", "declined", "removed" |
|
||
| permission | String | "editor", "viewer" |
|
||
|
||
---
|
||
|
||
## Pages et URLs
|
||
|
||
| URL | Description |
|
||
|-----|-------------|
|
||
| `/brainstorm` | Page principale du brainstorm |
|
||
| `/brainstorm?session=<id>` | Ouvrir une session spécifique |
|
||
| `/brainstorm?seed=<text>` | Auto-créer un brainstorm avec ce seed |
|
||
| `/brainstorm?sourceNoteId=<id>` | Créer depuis une note |
|
||
| `/brainstorm?invite=<token>` | Rejoindre via token d'invitation |
|
||
|
||
---
|
||
|
||
## API Routes
|
||
|
||
### `POST /api/brainstorm` — Créer un brainstorm
|
||
**Input** : `{ seedIdea, sourceNoteId?, contextNoteIds?, locale? }`
|
||
**Processus** :
|
||
1. Si pas de `contextNoteIds` → embedding du seed → recherche des 8 notes les plus proches
|
||
2. LLM classe chaque note : SUPPORT, TENSION, ou EXTENSION
|
||
3. LLM génère 9 idées en 3 vagues :
|
||
- **Wave 1 (Variations)** : 3 idées, dont au moins 1 de SUPPORT, 1 répondant à TENSION
|
||
- **Wave 2 (Analogies)** : 3 idées, dont au moins 1 d'EXTENSION, 1 transposition de pattern
|
||
- **Wave 3 (Disruptions)** : 3 idées, dont au moins 1 inversion de SUPPORT, 1 synthèse de contradictions
|
||
4. Chaque idée a : wave, title, description, connectionToSeed, noveltyScore, noteRefs[]
|
||
5. Positionnement radial : angle = `(idx % 3) * (2π/3) + (wave-1) * 0.5`, radius = `wave * 150`
|
||
6. Crée session + host participant + log activity + 9 idées avec noteRefs
|
||
**Retour** : Session complète + `{ support, tension, extension }` counts
|
||
|
||
### `GET /api/brainstorm` — Lister les sessions de l'utilisateur
|
||
**Retour** : `BrainstormSessionListItem[]` (id, seedIdea, totalIdeas, activeIdeas, dates)
|
||
|
||
### `GET /api/brainstorm/shared` — Sessions partagées acceptées
|
||
**Processus** : Fetch shares where userId + accepted, upsert participant si manquant
|
||
**Retour** : Même format que la liste + `_isShared: true`
|
||
|
||
### `GET /api/brainstorm/[sessionId]` — Détail d'une session
|
||
**Accès** : Propriétaire OU participant OU share accepted
|
||
**Retour** : Session complète avec ideas (ordonnées par wave/date), noteRefs, creator, sourceNote, exportedNote
|
||
|
||
### `DELETE /api/brainstorm/[sessionId]` — Supprimer une session
|
||
**Accès** : Propriétaire uniquement
|
||
|
||
### `POST /api/brainstorm/[sessionId]/expand` — Approfondir une idée
|
||
**Accès** : Propriétaire uniquement
|
||
**Input** : `{ ideaId, locale? }`
|
||
**Processus** :
|
||
1. Charge les noteRefs de l'idée parent + 5 notes via embedding
|
||
2. LLM génère 9 sous-idées en 3 vagues relatives au parent
|
||
3. Positionnement radial autour du parent
|
||
4. Crée les idées avec `parentIdeaId = source idea`
|
||
**Retour** : Session mise à jour
|
||
|
||
### `POST /api/brainstorm/[sessionId]/manual-idea` — Ajouter une idée manuelle
|
||
**Accès** : Participant avec rôle editor
|
||
**Input** : `{ title, description?, parentIdeaId?, locale? }`
|
||
**Processus** :
|
||
1. Wave = `min(parent.wave + 1, 3)` si parent, sinon 1
|
||
2. Crée l'idée avec `createdBy = userId`, `createdByType = 'human'`
|
||
3. Auto-link : embedding → 3 notes les plus proches → BrainstormNoteRef avec relation "extends"
|
||
4. **Enrichissement IA** (non-bloquant) : LLM polit le titre, enrichit la description, set noveltyScore. Respecte la locale (`IMPORTANT: You MUST write ALL text in ${lang}`)
|
||
**Retour** : Session mise à jour (201)
|
||
|
||
### `POST /api/brainstorm/[sessionId]/dismiss` — Rejeter une idée
|
||
**Accès** : Participant avec rôle editor
|
||
**Input** : `{ ideaId }`
|
||
**Processus** : Transaction → status = "dismissed" + tous noteRefs verdict = "dismissed"
|
||
|
||
### `POST /api/brainstorm/[sessionId]/convert` — Convertir en note
|
||
**Accès** : Propriétaire uniquement
|
||
**Input** : `{ ideaId }`
|
||
**Processus** :
|
||
1. Crée une Note avec :
|
||
- Titre = titre de l'idée
|
||
- Contenu = markdown formaté (description, connection, novelty, source brainstorm, noteRefs avec relations)
|
||
- Labels = `['brainstorm', 'idée']`
|
||
- Même notebook que la note source
|
||
2. Transaction : status = "converted", convertedToNoteId = note.id, noteRefs verdict = "accepted", tag les notes référencées avec "brainstorm-fruitful"
|
||
**Retour** : Note créée (201)
|
||
|
||
### `POST /api/brainstorm/[sessionId]/finalize` — Finaliser la session
|
||
**Accès** : Propriétaire uniquement
|
||
**Processus** :
|
||
- Pour chaque note référencée : si tous les refs sont dismissed → tag "brainstorm-dry"
|
||
- Compte notesEnriched (≥1 accepted) et notesMarkedDry (all dismissed)
|
||
**Retour** : `{ notesSolicited, notesEnriched, notesMarkedDry }`
|
||
|
||
### `POST /api/brainstorm/[sessionId]/export` — Exporter en note
|
||
**Accès** : Propriétaire uniquement
|
||
**Processus** :
|
||
- Génère un markdown complet : header, summary, sections par wave, notes sollicitées, notes converties
|
||
- Crée une Note avec labels `['brainstorm', 'export']`
|
||
- Update session.exportedNoteId
|
||
**Retour** : Note créée (201)
|
||
|
||
### `POST /api/brainstorm/[sessionId]/update-position` — Sauvegarder la position d'un nœud
|
||
**Input** : `{ ideaId, positionX, positionY }`
|
||
|
||
### `POST /api/brainstorm/[sessionId]/invite` — Créer une invitation
|
||
**Accès** : Host uniquement
|
||
**Input** : `{ role, expiresInHours?, email? }`
|
||
- Si `email` fourni : trouve l'utilisateur, crée une Notification, retourne `{ mode: 'email' }`
|
||
- Sinon : génère un token, retourne `{ mode: 'link', inviteUrl }`
|
||
|
||
### `POST /api/brainstorm/join` — Rejoindre via token
|
||
**Input** : `{ token }`
|
||
**Processus** : Vérifie token + expiry → crée BrainstormParticipant → notifie le owner
|
||
|
||
### `GET /api/brainstorm/[sessionId]/activity` — Feed d'activité
|
||
**Retour** : 50 dernières activités avec user info
|
||
|
||
---
|
||
|
||
## Server Actions (`app/actions/brainstorm.ts`)
|
||
|
||
### `createBrainstormShare(sessionId, recipientEmail, permission?)`
|
||
Partage par email (même pattern que NoteShare).
|
||
1. Vérifie que le user est propriétaire
|
||
2. Cherche le recipient par email
|
||
3. Gère les cas existants :
|
||
- `accepted` → retourne `{ message: 'already_shared' }` (succès info)
|
||
- `pending` → retourne `{ message: 'already_pending' }` (succès info)
|
||
- `declined/removed` → ré-invite (remet à pending)
|
||
4. Sinon → crée un `BrainstormShare` avec status pending
|
||
|
||
### `respondToBrainstormShare(shareId, action)`
|
||
Accepter ou refuser un partage.
|
||
- Accept → upsert `BrainstormParticipant` (idempotent)
|
||
- Decline → update status
|
||
|
||
### `getPendingBrainstormShares()`
|
||
Retourne les shares pending pour l'utilisateur courant (avec session + sharer info).
|
||
|
||
### `getAcceptedBrainstormShares()`
|
||
Retourne les shares accepted (pour afficher dans la sidebar).
|
||
|
||
### `removeBrainstormShare(sessionId)`
|
||
Passe le share en status "removed".
|
||
|
||
---
|
||
|
||
## Interface utilisateur — Page Brainstorm
|
||
|
||
### Layout global
|
||
```
|
||
┌──────────────────────────────────────────────────────────────┐
|
||
│ HEADER (fixed, border-bottom, backdrop-blur) │
|
||
│ [Wind icon] Waves of Thought │
|
||
│ "Unfold dimensions of potentiality" │
|
||
│ [__________ input seed idea __________] [+] │
|
||
│ • • • AI is harvesting seeds of thought... │
|
||
├──────────────────────────────────┬─────────────┬────┐ │
|
||
│ │ │ │ │
|
||
│ CANVAS (D3) │ DETAIL │ S │ │
|
||
│ │ PANEL │ I │ │
|
||
│ ○ ring 1 │ (400px) │ D │ │
|
||
│ ○ ring 2 │ │ E │ │
|
||
│ ○ ring 3 │ │ B │ │
|
||
│ │ │ A │ │
|
||
│ [toolbar flottant en bas] │ │ R │ │
|
||
│ │ │ │ │
|
||
├──────────────────────────────────┴─────────────┴────┘ │
|
||
│ [Activity Feed panel] (slide depuis la droite) │
|
||
│ [BrainstormShareDialog] (modal) │
|
||
│ [Impact Toast] (fixed bottom center) │
|
||
└──────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
### Header
|
||
- **Icône** : `<Wind>` dans un carré orange qui tourne pendant la génération
|
||
- **Titre** : "Waves of Thought" (serif)
|
||
- **Sous-titre** : "Unfold dimensions of potentiality" (muted)
|
||
- **Input seed** : Grand champ serif italic avec glow gradient au focus
|
||
- **Bouton submit** : `<Plus>` icône, positionné à droite dans l'input, disabled si vide ou en génération
|
||
- **Indicateur de génération** : 3 points orange pulsants + texte "AI is harvesting seeds of thought..."
|
||
|
||
### Canvas D3 (WaveCanvas)
|
||
|
||
#### Éléments visuels
|
||
- **Fond** : `#F8F7F2` avec grille de points (20px)
|
||
- **3 anneaux** : rayons 200, 400, 600 — traits gris pointillés, opacité 0.5
|
||
- **Nœud racine** (seed) :
|
||
- Cercle noir (#141414), rayon 40
|
||
- Texte "SEED" en blanc, 10px bold
|
||
- Label de l'idée graine en serif italic 18px, positionné à y=80
|
||
- **Nœuds idée** :
|
||
- Cercle blanc avec bordure colorée par wave (orange/blue/violet)
|
||
- Rayon 28 (18 si dismissed)
|
||
- Texte : titre tronqué à 18 caractères, positionné sous le cercle
|
||
- Badges :
|
||
- **Converti** : fond vert + checkmark ✓
|
||
- **Notes liées** : emoji 📎 en haut-gauche
|
||
- **Créé par humain** : cercle bleu avec initiale en haut-droite
|
||
- **Créé par IA** : symbole ✦ violet en haut-droite
|
||
- **Dismissed** : opacité 0.3
|
||
- **Liens** :
|
||
- Wave (racine → idée) : gris pointillé, 1.5px
|
||
- Parent (idée → sous-idée) : jaune solid, 2px
|
||
|
||
#### Forces D3
|
||
- `forceLink` : distance = wave × 200 (wave links), 180 (parent links)
|
||
- `forceManyBody` : strength -800 (répulsion)
|
||
- `forceRadial` : rayon = wave × 200, strength 0.8
|
||
- `forceCollide` : radius + 30
|
||
|
||
#### Interactions
|
||
| Action | Effet |
|
||
|--------|-------|
|
||
| **Clic sur nœud** | Sélectionne l'idée → ouvre le panneau détail |
|
||
| **Double-clic sur nœud** | Ouvre l'éditeur inline pour créer un enfant |
|
||
| **Double-clic sur canvas** | Ouvre l'éditeur inline pour créer une idée racine |
|
||
| **Drag d'un nœud** | Déplace le nœud, fixe la position, émet `idea:moved` au socket |
|
||
| **Zoom/Pan** | d3.zoom, échelle [0.1, 5], centré avec scale 0.8 |
|
||
|
||
#### Éditeur inline (double-clic)
|
||
Apparaît sous le point de clic, carte flottante (260px) :
|
||
- En-tête : icône "+" bleue + label "Réponse" ou "Nouvelle idée"
|
||
- Input serif avec fond subtil
|
||
- Raccourcis : `↵ enregistrer` · `esc annuler`
|
||
- Si enfant : label "→ enfant"
|
||
- Triangle pointeur sous la carte
|
||
- **Enter** → appelle `onCreateIdea({ title, parentIdeaId, x, y })`
|
||
- **Escape** → ferme
|
||
|
||
#### Stabilité du canvas
|
||
- Le useEffect D3 ne se redéclenche QUE quand `sessionId` ou `ideasKey` (string sérialisée) changent
|
||
- Tous les callbacks (onNodeSelect, onPositionUpdate, onCreateIdea) sont passés via refs (`onNodeSelectRef.current`) pour éviter les re-renders
|
||
- Les remote moves (socket) mettent à jour directement les nœuds D3 sans re-render React
|
||
|
||
### Toolbar flottante (bas du canvas)
|
||
Pilule animée qui apparaît quand une session est active :
|
||
| Élément | Style | Action |
|
||
|---------|-------|--------|
|
||
| **Wave 1/2/3** | 3 points colorés (orange/blue/violet) | Légende visuelle |
|
||
| **Avatars** | Cercles colorés empilés (max 4, "+N") | Présence des utilisateurs connectés |
|
||
| **Exporter** | Texte orange | Appelle `exportBrainstorm` puis `finalizeBrainstorm` |
|
||
| **Inviter** | Texte émeraude, icône `<UserPlus>` | Ouvre `BrainstormShareDialog` |
|
||
| **Activité** | Icône `<Activity>`, muted/orange si actif | Toggle le panneau Activity Feed |
|
||
| **Supprimer** | Icône poubelle, muted → rose au hover | Supprime la session |
|
||
|
||
### Panneau détail (droite, 400px, slide animé)
|
||
Apparaît quand une idée est sélectionnée :
|
||
|
||
| Section | Contenu |
|
||
|---------|---------|
|
||
| **Badge wave** | Pilule colorée (Wave 1 orange / Wave 2 blue / Wave 3 violet) |
|
||
| **Note créée** | Badge vert si convertie |
|
||
| **Fermer** | Bouton chevron droite |
|
||
| **Titre** | 3xl serif bold |
|
||
| **Score de nouveauté** | `<Zap>` + nombre |
|
||
| **Créateur** | Humain = cercle bleu + initiale, IA = spark violet + "IA" |
|
||
| **Description** | Texte de l'idée |
|
||
| **Connexion au seed** | Carte slate, citation italic |
|
||
| **Origine de l'idée** | Cartes noteRef avec badge de relation (emerald=positive, rose=opposes, amber=autre), bouton "View" |
|
||
| **Bouton Approfondir** | `<Wind>` + "Deepen", bordure dashed, hover orange → `expandIdea` |
|
||
| **Bouton Créer Note** | `<FileText>` + "Create Note", bordure dashed, hover émeraude → `convertIdea` |
|
||
| **Bouton Non pertinent** | Texte muted, hover rose → `dismissIdea` |
|
||
|
||
### Sidebar sessions (bande droite, 64px)
|
||
- Icône `<History>` en haut
|
||
- Liste scrollable de boutons circulaires (40px) avec la première lettre du seed
|
||
- **Actif** : fond sombre, texte blanc, scale 110, shadow
|
||
- **Partagé** : fond orange clair, texte orange
|
||
- **Normal** : fond blanc, texte muted
|
||
- Filet décoratif en bas
|
||
|
||
### Activity Feed (panneau coulissant, 320px)
|
||
Slide depuis la droite avec animation spring :
|
||
- En-tête : icône + "Activity" + bouton fermer
|
||
- État vide : "No activity yet" italic
|
||
- Items : icône par type d'action, username bold, label d'action, titre idée (tronqué 30 chars), temps relatif (1m/1h/1d)
|
||
- Types : `manual_idea` (ampoule bleue), `wave_generated` (éclair orange), `joined` (user+ vert), `idea_dismissed` (X rose), `invite_created` (user+ violet)
|
||
|
||
### BrainstormShareDialog (modal)
|
||
- Trigger : bouton "Inviter" de la toolbar
|
||
- Header : icône orange + "Partager le brainstorm" + seed idea tronqué
|
||
- Champ email avec label "Adresse email"
|
||
- Bouton "Partager" orange avec `<UserPlus>`
|
||
- Messages de feedback :
|
||
- Succès (vert + ✓) : "Invitation envoyée!" / "Invitation renvoyée!"
|
||
- Info (ambre + ⚠) : "Cette personne a déjà accès" / "Invitation déjà en attente"
|
||
- Erreur (rose + ⚠) : "No account found with this email" etc.
|
||
- Note : "La personne recevra une notification pour accepter ou refuser."
|
||
|
||
### Impact Toast (fixed bottom center)
|
||
Apparaît après Export + Finalize :
|
||
- "X note(s) enriched / X note(s) marked dry"
|
||
- Disparaît après 4 secondes
|
||
|
||
---
|
||
|
||
## Sidebar gauche (app principale)
|
||
|
||
### Section Brainstorms (`SidebarBrainstorms`)
|
||
- **Vue vide** : Icône `<Sparkles>` orange + "Aucune session" + "Démarrer →"
|
||
- **Loading** : 3 skeletons pulsants
|
||
- **Liste** (max 10) :
|
||
- **Sessions possédées** : Cercle orange + `<Sparkles>` + seed idea + count idées + date + bouton supprimer (rose au hover)
|
||
- **Sessions partagées** : Cercle bleu + `<Sparkles>` + seed idea + badge "partagé" + count idées + date (pas de bouton supprimer)
|
||
- Clic → navigate vers `/brainstorm?session={id}`
|
||
|
||
### Navigation
|
||
- Bouton "brainstorms" dans le toggle de vues (icône `<Sparkles>` orange, highlight orange quand actif)
|
||
|
||
---
|
||
|
||
## Notifications (cloche)
|
||
|
||
### Partages brainstorm pending
|
||
- Section dédiée entre les reminders et les share requests de notes
|
||
- Avatar orange gradient avec initiale de l'envoyeur
|
||
- Icône `<Wind>` + label "BRAINSTORM" orange
|
||
- Nom de l'envoyeur + "invited you to a brainstorm" + aperçu du seed (tronqué 35 chars)
|
||
- Boutons Accepter (orange) / Refuser
|
||
|
||
### Notifications système
|
||
- `brainstorm_invite` : icône `<Wind>` émeraude
|
||
- `brainstorm_joined` : icône `<Wind>` bleue
|
||
|
||
---
|
||
|
||
## Temps réel (Socket.io)
|
||
|
||
### Événements client → serveur
|
||
| Event | Data | Quand |
|
||
|-------|------|-------|
|
||
| `cursor:move` | `{ x, y }` ou `null` | Mouvement souris sur le canvas |
|
||
| `idea:moved` | `{ ideaId, positionX, positionY, userId }` | Fin de drag d'un nœud |
|
||
| `activity:new` | `{ action, userId, userName, details }` | Action utilisateur |
|
||
|
||
### Événements serveur → client
|
||
| Event | Data | Effet |
|
||
|-------|------|-------|
|
||
| `presence:update` | `PresenceUser[]` | Mise à jour de la liste des utilisateurs connectés |
|
||
| `cursor:update` | `{ userId, cursor: {x,y} }` | Déplacement du curseur d'un autre user |
|
||
| `activity:new` | `ActivityEvent` | Nouvelle activité dans le feed |
|
||
| `idea:moved` | `{ ideaId, positionX, positionY }` | Déplacement d'un nœud par un autre user |
|
||
| `idea:added` | — | Placeholder (non utilisé côté client) |
|
||
| `idea:dismissed` | — | Placeholder (non utilisé côté client) |
|
||
|
||
### Flux de déplacement en temps réel
|
||
1. User A drag un nœud → fin de drag → `handlePositionUpdate` → émet `idea:moved` + POST API (persist)
|
||
2. Socket server → broadcast `idea:moved` aux autres dans la room
|
||
3. User B → `useBrainstormSocket` callback → `setRemoteMove(...)` (avec compteur séquentiel)
|
||
4. WaveCanvas → `useEffect([remoteMove])` → fixe fx/fy du nœud cible → 30 ticks de simulation → update positions liens/nœuds directement dans D3
|
||
|
||
### Curseurs live
|
||
- Chaque user a un curseur coloré (SVG arrow + nom) affiché sur le canvas des autres
|
||
- 12 couleurs assignées en round-robin
|
||
- `useCursorTracking` attache un listener mousemove au container et émet `cursor:move`
|
||
|
||
### Ghost Cursor IA
|
||
- Quand une vague IA est en génération, un curseur violet animé apparaît
|
||
- Se déplace aléatoirement dans un rayon de 150-350px du centre
|
||
- Pulsation violette + badge "AI ✦"
|
||
|
||
---
|
||
|
||
## Flux de partage
|
||
|
||
### Par email (server action — principal)
|
||
```
|
||
Propriétaire → Dialog email → createBrainstormShare()
|
||
→ Cherche recipient par email
|
||
→ Crée BrainstormShare (pending)
|
||
→ Destinataire voit dans NotificationPanel (cloche)
|
||
→ Accept → Upsert BrainstormParticipant
|
||
→ Decline → Update status
|
||
→ Apparaît dans sidebar (bleu, badge "partagé")
|
||
```
|
||
|
||
### Par lien (API invite — secondaire)
|
||
```
|
||
Propriétaire → invite route → génère token → copie URL
|
||
→ Destinataire ouvre URL avec ?invite=<token>
|
||
→ join route → vérifie token + expiry
|
||
→ Crée BrainstormParticipant
|
||
→ Notifie le owner
|
||
```
|
||
|
||
---
|
||
|
||
## Hooks React Query
|
||
|
||
| Hook | Type | Query Key | API |
|
||
|------|------|-----------|-----|
|
||
| `useBrainstormSessions()` | Query | `['brainstorm', 'sessions']` | GET /api/brainstorm |
|
||
| `useSharedBrainstormSessions()` | Query | `['brainstorm', 'shared-sessions']` | GET /api/brainstorm/shared |
|
||
| `useBrainstormSession(id)` | Query | `['brainstorm', 'session', id]` | GET /api/brainstorm/{id} |
|
||
| `useCreateBrainstorm()` | Mutation | — | POST /api/brainstorm |
|
||
| `useExpandIdea(id)` | Mutation | — | POST /api/brainstorm/{id}/expand |
|
||
| `useDismissIdea(id)` | Mutation | — | POST /api/brainstorm/{id}/dismiss |
|
||
| `useConvertIdea(id)` | Mutation | — | POST /api/brainstorm/{id}/convert |
|
||
| `useExportBrainstorm(id)` | Mutation | — | POST /api/brainstorm/{id}/export |
|
||
| `useFinalizeBrainstorm(id)` | Mutation | — | POST /api/brainstorm/{id}/finalize |
|
||
| `useDeleteBrainstorm()` | Mutation | — | DELETE /api/brainstorm/{id} |
|
||
| `useAddManualIdea(id)` | Mutation | — | POST /api/brainstorm/{id}/manual-idea |
|
||
| `useBrainstormActivity(id)` | Query | — | GET /api/brainstorm/{id}/activity (refetch 10s) |
|
||
|
||
---
|
||
|
||
## Fichiers sources
|
||
|
||
| Fichier | Rôle |
|
||
|---------|------|
|
||
| `components/brainstorm/brainstorm-page.tsx` | Page principale complète |
|
||
| `components/brainstorm/wave-canvas.tsx` | Canvas D3 (noeuds, liens, interactions) |
|
||
| `components/brainstorm/brainstorm-share-dialog.tsx` | Modal de partage par email |
|
||
| `components/brainstorm/activity-feed.tsx` | Panneau d'activité |
|
||
| `components/brainstorm/ghost-cursor.tsx` | Curseur IA animé |
|
||
| `components/brainstorm/live-cursors.tsx` | Curseurs des autres users + avatars |
|
||
| `hooks/use-brainstorm.ts` | Tous les hooks React Query |
|
||
| `hooks/use-brainstorm-socket.ts` | Hook Socket.io |
|
||
| `app/actions/brainstorm.ts` | Server actions (partage) |
|
||
| `app/api/brainstorm/route.ts` | POST create + GET list |
|
||
| `app/api/brainstorm/[sessionId]/route.ts` | GET/DELETE session |
|
||
| `app/api/brainstorm/[sessionId]/expand/route.ts` | POST approfondir |
|
||
| `app/api/brainstorm/[sessionId]/manual-idea/route.ts` | POST idée manuelle |
|
||
| `app/api/brainstorm/[sessionId]/dismiss/route.ts` | POST rejeter |
|
||
| `app/api/brainstorm/[sessionId]/convert/route.ts` | POST convertir en note |
|
||
| `app/api/brainstorm/[sessionId]/finalize/route.ts` | POST finaliser |
|
||
| `app/api/brainstorm/[sessionId]/export/route.ts` | POST exporter |
|
||
| `app/api/brainstorm/[sessionId]/update-position/route.ts` | POST position |
|
||
| `app/api/brainstorm/[sessionId]/invite/route.ts` | POST invitation par lien |
|
||
| `app/api/brainstorm/[sessionId]/activity/route.ts` | GET activités |
|
||
| `app/api/brainstorm/join/route.ts` | POST rejoindre par token |
|
||
| `app/api/brainstorm/shared/route.ts` | GET sessions partagées |
|
||
| `socket-server.ts` | Serveur Socket.io |
|
||
| `types/brainstorm.ts` | Types TypeScript |
|
||
| `lib/brainstorm-collab.ts` | verifyParticipant + logActivity |
|
||
| `prisma/schema.prisma` | Modèles DB (lignes 454-572) |
|
||
| `components/notification-panel.tsx` | Notifications partage brainstorm |
|
||
| `components/sidebar.tsx` | SidebarBrainstorms (lignes 85-169) |
|
||
|
||
---
|
||
|
||
## Composants obsolètes (existent mais non utilisés)
|
||
|
||
| Fichier | Note |
|
||
|---------|------|
|
||
| `components/brainstorm/manual-idea-dialog.tsx` | Remplacé par l'éditeur inline du canvas |
|
||
| `components/brainstorm/invite-dialog.tsx` | Remplacé par BrainstormShareDialog |
|
||
| `components/brainstorm/brainstorm-create-dialog.tsx` | Remplacé par l'input inline du header |
|
||
| `components/brainstorm/brainstorm-canvas.tsx` | Ancienne implémentation avec react-force-graph-2d, remplacé par WaveCanvas (D3 direct) |
|