Flux métier
Les workflows critiques du Board : lancement d'atelier, distribution et gestion des lots, undo/redo, collaboration temps réel.
Lancement d'atelier et chargement initial
Détails :
- Animateur clique le bouton « Animer » dans la fiche atelier LeHub.
- Le Hub génère un JWT signé avec
BOARD_ADMIN_JWT_SECRET, inclutaud: 'board-admin'et l'ID animateur. - Redirection vers
https://board.fresquesystemique.org/b/[token]?admin=<jwt>. - LeBoard fetch l'état initial via
GET /api/board/[token](validation JWT + board access). - Rendu page React avec état initial (cartes, post-its, étape active, etc.).
- Socket.io connect depuis le client → création
AnimatorSessioncôté serveur (durée 24h). - Broadcast
board-stateà la connexion pour synchroniser l'état avec tous les participants actuels.
Participant rejoint le plateau
- Participant reçoit/clique URL publique
https://board.fresquesystemique.org/b/[token]. - LeBoard valide le token (lookup Board par token).
- Page charge avec l'état initial board (cartes déjà posées, post-its, étape masquée).
- Modal
PseudoModaldemande le pseudo (ou utilise pseudo stocké localement). - Socket.io connect → broadcast
board-stateà la connexion pour synchroniser l'état, affichage pseudo + curseur.
Distribution lot (Distribuer)
Détails :
- Animateur clique « Distribuer » pour un lot depuis le
LotPanel. - Émission Socket
lot:distributeavec l'ID lot. - Serveur :
- Récupère le lot du Hub (cardIds JSON).
- Upsert
BoardLotDistributionavecmode='distribute'. - Mélange aléatoire des cardIds.
- Répartition round-robin : participant 1 reçoit cartes 1,3,5,… / participant 2 reçoit 2,4,6,…
- Chaque participant a sa pioche unique (invisible des autres).
- Broadcast
lot:distributedavec payload{[participantId]: [cardIds]}(chiffré par socket room). - Chaque participant voit ses cartes dans le
DeckPanel(bas écran, max 5 visibles).
Lancement lot (Pop-corn)
Détails :
- Animateur clique « Pop-corn » pour un lot avec matrice (ex: étape "Système A").
- Serveur upsert
BoardLotDistributionavecmode='popcorn'. - Pour chaque carte du lot avec mapping matrice (col, row) :
- Récupère la cellule correspondante.
- Calcule position de snap (
snap-to-gridviafindSnapPosition()). - Crée/met à jour
CardPlacementavec (x, y) calculées.
- Broadcast
lot-distributeavec liste cardIds et placements. - Animateur voit les cartes auto-posées dans la grille, verrouillées.
- Les cartes restent dans la main de l'animateur jusqu'à révélation manuelle au clic (toggle
hidden).
Déplacement carte (drag-drop)
Détails :
- Participant drag une carte depuis le plateau.
- Calcul optionnel de snap (si la carte doit rester dans une cellule de matrice).
- Émission
card-moveavec (cardId, x, y). - Serveur UPDATE la
CardPlacementen DB. - Broadcast
card-moveà tous les autres (le client émetteur anticipe via optimistic update, donc pas de re-broadcast). - Affichage temps réel (~< 100ms latence socket).
Émergences indésirables
Certains lots sont marqués kind='emergence' au Hub. Leurs cartes s'ancrent à une carte de base déjà posée.
- Lot "Émergence Actions" lancé en pop-corn.
- Chaque carte du lot a un
emergenceSlot(1 ou 2) et unanchorCardId. - Serveur calcule position d'émergence via
emergencePosition(anchorCard)+ slot offset. - Cartes s'ancrent à la carte base et la suivent en cas de déplacement.
- Si carte base déplacée →
card:movedémis → serveur recalcule toutes les émergences liées et broadcast.
Flèches de causalité
Mode lien classique (Connection)
- Animateur/participant active l'outil Flèche.
- Clique sur anchorpoint de carte A →
arrowDraftstate. - Clique sur anchorpoint de carte B → création
Connectionen DB. - Broadcast
arrow:created→ rendu KonvaArrow ligne droite.
Mode flèche avancée (BoardArrow)
- Pareil, mais crée un
BoardArrowavec couleur/direction/strokeWidth. - Supprimer flèche : clic droit →
arrow:delete→ suppressionConnectionouBoardArrow.
Undo/Redo
Historique animateur (global)
- Animateur fait action (déplacer carte, créer post-it, etc.).
- Changement enregistré en mémoire dans
undoStack(serveur Socket.io). - Clique Ctrl+Z → serveur cherche l'action précédente, la réverse, broadcast aux autres (lecture seule).
Note : l'undo global animateur impacte le state serveur (vérité), broadcast aux participants qui voient le changement.
Historique participant (personnel)
- Participant fait action.
- Changement enregistré localement (pas de broadcast serveur).
- Ctrl+Z → socket
undo:personal→ serveur marque comme "local-undo" (pas broadcast). - Le changement est annulé localement seulement.
Remarque : LeBoard n'a pas (encore) d'historique persistant stocké en BD. L'undo/redo vit en mémoire serveur et es perdu au redéploiement. C'est acceptable pour un atelier qui dure 2-3h.
Étape active et forçage suivi
Animateur change l'étape
- Animateur clique l'étape suivante dans
BottomNav. - Socket
stage-change→ serveur updateBoard.activeStageId. - Broadcast
stage-changeà tous pour synchroniser l'étape active. - Suivi de viewport (optionnel) : animateur peut envoyer
viewport-changeavec ses coordonnées zoom/pan → les participants en suivi reçoivent et adaptent leur viewport.
Participant suit l'animateur (volontaire)
- Participant clique pseudo de l'animateur dans
ParticipantBar→ émetstart-follow(pseudo de l'animateur). - Serveur broadcast
start-followà tous. - À chaque
viewport-changede l'animateur → les followers s'alignent. - Reclique pseudo pour arrêter (
stop-follow).
Réinitialisation plateau
- Animateur clique bouton Réinitialiser (reset).
- Socket
board-reset→ serveur efface l'état temps réel (cartes, post-its, étape). - Broadcast
board-resetà tous. - Plateau revient à l'état vide, prêt pour un nouvel atelier.
Collaboration temps réel (Socket.io)
Gestion des connexions
connection(joinémis) : création AnimatorSession (si JWT valide) ou simple user join.disconnection: suppressionAnimatorSession, suppression de l'utilisateur de la liste des participants.- Timeout inactivité : 24h → auto-suppression
AnimatorSession.
Événements clés
| Événement | Émetteur | Récepteur | Payload |
|---|---|---|---|
join | Client | Serveur | {pseudo, role, avatarUrl, authSessionId} |
card-move | Client | Serveur | {cardId, x, y} |
card-move | Serveur | Clients | {cardId, x, y} (broadcast) |
card-flip | Client | Serveur | {cardId, flipped} |
card-flip | Serveur | Clients | {cardId, flipped} (broadcast) |
card-drag-start | Client | Serveur | {cardId, pseudo} |
card-drag-end | Client | Serveur | {cardId} |
card-drag-end | Serveur | Clients | {cardId} (broadcast) |
card-placed | Client | Serveur | {cardId, x, y, flipped, zIndex} |
arrow-add | Client | Serveur | {fromId, toId, fromAnchor, toAnchor, ...} |
arrow-add | Serveur | Clients | Données flèche créée (broadcast) |
arrow-update | Client | Serveur | {arrowId, ...updates} |
arrow-update | Serveur | Clients | Données flèche modifiée (broadcast) |
arrow-delete | Client | Serveur | {arrowId} |
arrow-delete | Serveur | Clients | {arrowId} (broadcast) |
lot-distribute | Client (animateur) | Serveur | {lotId} |
deck-updated | Serveur | Clients | {[participantId]: [cardIds]} |
lot-correct-hands | Serveur | Clients | Correction distribution mains |
deck-assigned | Serveur | Clients | {participantId, cardIds} |
board-reset | Client (animateur) | Serveur | {} |
board-state | Serveur | Client | État complet du plateau (connexion) |
cursor-move | Client | Serveur | {pseudo, x, y} (throttlé) |
cursor-move | Serveur | Clients | {pseudo, x, y} (broadcast) |
start-follow | Client | Serveur | {pseudo} |
start-follow | Serveur | Clients | {pseudo} (broadcast) |
stop-follow | Client | Serveur | {} |
stop-follow | Serveur | Clients | {} (broadcast) |
slides-present | Client (animateur) | Serveur | {lotId} |
slides-present | Serveur | Clients | {lotId, slides: [...]} (broadcast) |
slides-navigate | Client | Serveur | {index} |
slides-navigate | Serveur | Clients | {index, pointer: {x, y}} (broadcast) |
slides-pointer | Client (animateur) | Serveur | {x, y} |
slides-pointer | Serveur | Clients | {x, y} (broadcast) |
slides-close | Client | Serveur | {} |
slides-close | Serveur | Clients | {} (broadcast) |
undo | Client (animateur) | Serveur | {} |
undo | Serveur | Clients | Action inversée (broadcast) |
redo | Client (animateur) | Serveur | {} |
redo | Serveur | Clients | Action réexécutée (broadcast) |
role-downgraded | Serveur | Client | {reason: 'session-invalid' | 'no-session'} |
Convention : les événements émis par le client vers le serveur déclenchent un broadcast du serveur aux autres clients avec le même nom ou une variante (ex: card-move côté client → card-move broadcast aux autres). Voir socket-handler.ts pour le détail de chaque handler.
Diapositives (SlideOverlay)
- Lot marqué
contentType='slides'lancé en pop-corn. - Serveur parse
Lot.slidesData(JSON images + notes). - Client affiche
SlideOverlayfullscreen. - Navigation : flèches gauche/droite, clic numéro slide.
- Pointeur partagé : position souris de l'animateur visible à tous.
- Broadcast
slides:navigate→ mise à jour index + pointeur.