Historique des versions du projet BuzzControl.
- PALMARES : fix définitif — endpoint dédié
GET /palmaresretourne le palmarès complet pré-assemblé côté backend (catégorie + nom + imageURL + couleur + points équipes/joueurs). Plus aucune race condition possible : le frontend n'a qu'un seul fetch à faire. (#107) - Dev : proxies Vite
/historyet/palmaresajoutés — PALMARES testable en mode développement.
- Nouvel endpoint
GET /palmares: agrégation sans double-comptage (clé compositeteam|player),ResolveCategoryMetaO(K catégories distinctes), tri desc partotalPoints, réponse[]garantie si historique vide - Frontend : suppression du
categoryStatsuseMemo (~80 lignes) — rendu direct depuisPalmaresEntry.name / .imageURL / .color / .teams / .players
- PALMARES : race condition définitivement supprimée —
/historyretourne désormaisCATEGORY_NAME,CATEGORY_IMAGE_URL,CATEGORY_COLORdans chaque événement (résolu côté backend au moment de l'enregistrement). PALMARES n'a plus besoin de synchroniser deux fetches REST indépendants. (#108) - PALMARES :
catKnown— catégorie avec nom embarqué mais sans image affiche correctement l'icône emoji (pas❓). (#108)
GameEvent: 3 nouveaux champsomitempty(CATEGORY_NAME,CATEGORY_IMAGE_URL,CATEGORY_COLOR) — rétrocompatibles avec l'historique existantResolveCategoryMeta(key): résolution des métadonnées catégorie (hardcodées + custom avec sidecar) utilisée aux 3 sites d'enregistrement d'événements
- PALMARES — race condition catégories : suppression de
refetchCategories()dans l'effet PALMARES — cet appel annulait le fetch initial deuseCategories(cleanup fetchTick 0→1), empêchant les catégories custom d'apparaître. Le fetch initial de mount suffit, les catégories étant disponibles bien avant la fin de partie. useCategories— null guard :setCategories(data ?? [])protège contre une réponse APInull(cas défensif).
- PALMARES — race condition catégories custom : le bloc PALMARES ne s'affiche plus avant que
GET /api/categoriesait répondu (loading guard). Élimine le❓persistant en cas de chargement lent ou d'ouverture du TV pendant PALMARES. - PALMARES — retry automatique : si
GET /api/categorieséchoue silencieusement, un retry automatique est déclenché 2 secondes après, sans action utilisateur. - PALMARES — nom original des catégories custom : le backend persiste maintenant le nom original saisi lors de la création (sidecar JSON).
GET /api/categoriesretourne"Sport Extrême"au lieu de"SPORT_EXTREME". Rétrocompatible (les catégories créées avant v5.7.7 continuent d'afficher leur nom technique). - PALMARES — normalize défensif sur les clés : le lookup de catégorie tolère les variations de casse entre l'historique de jeu et les clés API.
- #106 — Palmarès : image et couleur des catégories. Le bloc PALMARES utilise désormais un lookup direct sur
apiCategories(GET /api/categories) au lieu decategoryMeta(). Le backend est la seule source de vérité pourname,imageURLetcolor.- Frontend ne distingue plus custom vs hardcoded
CategoryInfoexpose désormais un champcolor(8 couleurs accent pour catégories hardcodées)- Noms de 3 catégories hardcodées enrichis :
Arts & Littérature,Sciences & Nature,Sports & Loisirs - Refetch automatique des catégories à l'entrée en PALMARES pour garantir données fraîches
- Icône emoji correcte par catégorie hardcodée (via dict
CATEGORIES) en l'absence d'image uploadée - Fallback couleur
#6b7280correctement appliqué aux catégories custom (chaîne vide""traitée par||)
- #105 — Palmarès et vues TV (READY, QCM, MEMORY, MEMOTION) : les catégories custom n'étaient jamais résolues en production car le filtre
isCustomretournait toujours une liste vide (champ absent de la réponse API). Correction : passage direct deapiCategoriescomplet àcategoryMeta(), suppression de la variablecustomCategoriesbasée surisCustom.
- #104 — Palmarès : l'image des catégories custom est désormais affichée (vignette 2rem×2rem) à la place de l'icône générique. Les catégories hardcodées conservent leur icône emoji.
- #102 — Palmarès : les catégories custom créées via le bouton '+' affichent désormais leur nom au lieu de "UNKNOWN". Résolution via
categoryMeta(key, customCategories); fallback en Title Case pour les clés non reconnues. - #103 — Boutons mode de jeu (page Quiz/Anim) : l'état non-sélectionné affiche maintenant la couleur du badge en version subtile (bordure + fond 10% opacité), cohérent avec l'état actif (50% opacité). MEMOTION et ARDOISE alignés sur la palette badge (amber / emerald).
- #100 — Bouton '+' catégorie : l'ajout d'une catégorie requiert désormais un nom et une image (multipart/form-data). Supprime la création JSON texte-only de v5.7.1.
POST /api/categoriesaccepte désormaismultipart/form-dataavec champsname+file(image obligatoire)GET /api/categoriesne scanne plus les fichiers.json; seules les images (png/jpg/jpeg/webp) sont retournées comme catégories custom- Limitation de taille :
http.MaxBytesReader(10 MB)pour uploads d'images - Clé auto-générée toujours via
toUpperSnakeCase(name), image sauvegardée comme<KEY>.<ext>
- #101 — Couleurs boutons mode de jeu (SPEEDY, QCM, MEMORY, MEMOTION, ARDOISE) alignées sur les couleurs de badge des cartes de questions
- Bordure : 100% couleur badge
- Fond : 50% opacité couleur badge
- Alias CSS
.type-speedyintroduit pour cohérence avec renommage NORMAL → SPEEDY
POST /api/categories: body JSON →multipart/form-data(champsname+fileobligatoire). Voircontracts/http-endpoints.md.GET /api/categories: suppression du scan des fichiers.json(uniquement images désormais).
- Création de catégories depuis l'UI (#97) : nouveau endpoint
POST /api/categoriespour créer une catégorie texte-only directement depuis QuestionsPage- Bouton "+" dans la zone filtres catégories (QuestionsPage)
- Dialog modal pour saisie du nom
- Catégorie créée immédiatement visible dans les filtres et badges
- Renommage NORMAL → SPEEDY (#98) : migration rétrocompatible du type de question
- Frontend : affichage "Speedy" au lieu de "Normal"
- Backend : détection version fichier, conversion silencieuse NORMAL → SPEEDY à la lecture
- Fichiers existants NORMAL lus sans erreur (backward compatible)
- TV : affichage type "Speedy" au lieu de "Normal" en phase READY
- Backup sélectif (#99) : paramètre
backgroundsrenommé enmedias- Endpoints
/backup-select,/reset-select,/backup-restore - Query param :
backgrounds=true→medias=true - Répertoire :
files/backgrounds/renommé enfiles/medias/ - Catégories personnalisées sauvegardées sous
mediasau lieu debackgrounds
- Endpoints
- Catégories personnalisées (#95) : dépôt d'images dans
data/files/categories/pour créer ses propres catégories- Endpoint
GET /api/categories: fusion catégories hardcodées + customs - Formats acceptés : PNG, JPG, JPEG, WEBP
- Clé auto-générée :
Sport Extreme.png→SPORT_EXTREME - Backup / restore / reset inclus via le flag
backgrounds - UI : composant
CategoryBadgeunifié (QuestionsPage, GamePage, TV) ; utilitairecategoryUtils.js
- Endpoint
- Avertissement réseau (#96) : bandeau rouge en admin quand le serveur n'est accessible qu'en localhost
- Champ
NETWORK_ONLY_LOCALHOSTdans GameState (WebSocket push) - Détection Go toutes les 30 s via goroutine dédiée (network watchdog)
- Bandeau rouge dans l'interface admin (GamePage)
- Champ
- Affichage TV — phase READY : toutes les phases READY (NORMAL, ARDOISE, QCM, MEMORY, MEMOTION) affichent la catégorie courante (standard ou custom)
- Composant
ReadyCategoryDisplayrationalisé : code unique standard/custom, animations Framer Motion - Type de jeu affiché au-dessus du badge catégorie (NORMAL / ARDOISE / QCM / MEMOTION) avec couleur signature par type (bleu / jaune / vert / rose), animation d'entrée et idle néon
- Icône catégorie : spring d'entrée + wobble idle
- MEMORY : pas de label type de jeu (contexte visuel suffisant)
- Composant
- Badge catégorie dans
QuestionCardunifié viaCategoryBadge(cohérence visuelle) - Filtre catégorie dans
GamePagepropagé aux catégories custom - MEMORY READY : détection via
MEMORY_PARTICIPATING_TEAMS(fix timing WebSocket — la phase READY arrivait avant le push des équipes) - Image catégorie custom :
url.PathEscapesur le nom de fichier (support des noms avec espaces)
- QR code "Rejoindre le jeu" illisible sur TV ENROLL : logo emoji obstruait les modules de données → passage au niveau de correction d'erreur H (30%) et réduction du logo 18%→15% de la surface (#85)
- URL encodée dans le QR jeu corrigée :
/player→/(EnrollPage directe, sans redirect superflu) (#85) - Escaping des caractères spéciaux (
;,,,",\) dans les champs SSID/password du QR WiFi, conformément à la spec ZXing (#85)
escapeWifiStringextraite verssrc/utils/wifiUtils.jspour testabilité
-
Mode ARDOISE (#86, #87) : nouveau type de question avec clavier AZERTY/NUMPAD sur VPlayer
- Modèle
ArdoiseAnswer: texte + timestamp soumission - GameState.ARDOISE_ANSWERS : dictionnaire équipe → réponse saisie
- Éditeur QuestionsPage : sélection type ARDOISE + champ réponse attendue (ANSWER) + sélecteur clavier AZERTY/NUMPAD
- Engine
SetArdoiseAnswer()avec guards FSM - Action WebSocket
ARDOISE_INPUT(endpoint/ws/player)
- Modèle
-
Clavier Virtuel ARDOISE (#88) : composant ArdoiseKeyboard avec touche DEL
- Affichage automatique dès que question.TYPE === "ARDOISE"
- Actif (saisie traitée) : phase STARTED uniquement
- Verrouillé : STOP, PAUSE, REVEALED
- Envoi temps réel throttlé 200ms
- Saisie locale immédiate (affichage sans attendre le serveur)
-
Affichage réponses équipes — zone admin (#89) : panneau réponses en temps réel (pattern MEMORY)
- Phase STARTED/STOPPED/REVEALED : affichage des réponses ARDOISE saisies
-
TV REVEAL ARDOISE (#90) : bonne réponse + cartes équipes en grid (footer 50/50)
- Affichage texte réponses avec contraintes statiques (overflow:hidden, vh/vw)
- Bonne réponse visible en REVEALED
- VPlayer identification via 3-pass lookup (#91) : payload.ID → msg.ID → clientID
- Mapping ARDOISE_ANSWERS dans useWebSocket.js (#92)
- Reset clavier à la phase PREPARE (#93)
- Badge type "Ardoise" coloré sur QuestionCard (#94)
- Régression zone-media NORMAL/QCM — media zone (width/height 100% + object-fit:contain)
- Badges colorés par type de question (Normal/QCM/Memory/Memotion/Ardoise)
- Filtres type sur 2 lignes dans QuestionsPage (Normal·QCM·Memory / Memotion·Ardoise)
- MEMOTION Secret Mode (#76) : nouveau paramètre
MOTION_MEMORIZE_DURATION(secondes) sur les questions MEMOTION- Phase MEMORIZE : toutes les cartes visibles face RECTO + timer décompte sur TV
- Transition automatique MEMORIZE → GRID à expiration
- Phase GRID en mode SECRET : coordonnées (A1, B2…) remplacent les thèmes sur les cartes
- Sélection par coordonnée via clic admin — flash RECTO 0.5s → VERSO question (flow standard)
- Compatible tous modes de jeu (SOLO, CHACUN_SON_TOUR, TANT_QUE_JE_GAGNE)
- Rétrocompatible : MOTION_MEMORIZE_DURATION absent/0 → mode standard inchangé
- Éditeur QuestionsPage : champ "Durée mémorisation (s)" dans la section MEMOTION
- Admin GamePage : statut MEMORIZE + liste coordonnées↔thèmes pendant phase GRID
- Security (#82) : garde path traversal dans
HandleApplyUpdate— rejette les versions contenant../ou séparateurs de chemin → 400 Bad Request
- [MEMOTION] Layout fullscreen cards : chronomètre visible (hors overlay), header 1/6 + body 4/6 + footer 1/6 de la zone de jeu, réponse en footer (REVEAL), polices cqh
- [Server] Port TCP libéré immédiatement après arrêt (Linux + Windows) — SO_LINGER(0) sur chaque connexion acceptée via
lingerListenerwrapper → RST au lieu de FIN/ACK, no TIME_WAIT (#83) - [Server] SO_REUSEADDR posé explicitement sur le socket listen via
net.ListenConfig.Control(Linux : listen_posix.go, Windows : listen_windows.go)
- [#77] MEMOTION : refonte des 5 états visuels des cards (SELECTED fullscreen fond violet layout RECTO 1/6|4/6|1/6, VERSO QUESTION texte+image+vide, VERSO RÉPONSE rappel+answer+texte, RECTO DONE fond équipe+scale 80%+étoiles+nom équipe)
- MEMOTION fullscreen (SELECTED / QUESTION / REVEAL) : layout CSS grid
1fr 4fr 1fr— header thème en row 1 (1/6), image en row 2 (4/6), timer en row 3 (1/6) pour la sous-phase QUESTION ; placement explicite viagrid-rowindépendant de l'ordre DOM (#77) - MEMOTION fullscreen : textes agrandis pour lisibilité TV — thème
4rem, étoiles3rem, texte question/réponse4.5rem
- HTTPServer graceful shutdown: replaced
Close()withShutdown(ctx)(3s timeout) to eliminate TCP TIME_WAIT port-busy errors on restart (#81) - MEMOTION grid cards: layout 1/6 header + 4/6 body + 1/6 footer (CSS grid), fonts filling zone height via
cqhunits, imagesobject-fit: containto preserve aspect ratio (#77)
- Suppression du TCPServer legacy (port 1234 TCP) — dead code depuis v3.0.0 qui bloquait le démarrage sur Windows (#80)
- Port UDP 1234 (discovery firmware BuzzClick) découplé du champ config
tcp_portvia constanteBuzzerDiscoveryPort
- MEMOTION TV : layout constant des cartes en 3 zones fixes — header (titre/thème), body (image, flex-grow), footer (étoiles + points) ; supprime l'ancienne logique conditionnelle image/no-image (#77)
- MEMOTION : sélection de carte depuis la preview TV en subphase GRID — clic direct sur
PlayerDisplayen modeisAdminPreviewpour toute carteUNPLAYED(#78) - MEMOTION admin : panneau GRID simplifié — mini-grille supprimée, remplacée par un label informatif (#78)
- MEMOTION admin : subphase REVEAL — affiche uniquement le bouton de l'équipe courante + "Perdu" (remplace l'affichage de toutes les équipes avec buzzers) ; renommage "Aucun" → "Perdu" (#79)
- MEMOTION : points par niveau de difficulté configurables depuis l'éditeur (★=1pt / ★★=3pt / ★★★=5pt par défaut, modifiables par question)
- MEMOTION : inputs points difficulté — valeur minimale 1 (min="0" permettait de saisir 0 ignoré silencieusement)
- MEMOTION éditeur : sélecteur de difficulté affiche uniquement les étoiles (★/★★/★★★), sans les points qui n'étaient pas mis à jour dynamiquement
- MEMOTION éditeur : champ "Temps" remonté dans le bloc configuration en haut du formulaire (avec les points de difficulté)
- MEMOTION TV: cartes rectangulaires qui remplissent toute la zone de jeu (override
.memotion-game .memory-gridavec1fr, sans algo carré MEMORY) - MEMOTION TV: animation zoom clip-path fiable lors de la sélection d'une carte (getBoundingClientRect au rendu, remplace layoutId incompatible avec overflow:hidden)
- MEMOTION TV: animation zoom arrière vers la grille après attribution des points (DONE)
- MEMOTION TV: REVEAL — ancrage clipPath pour permettre l'interpolation CSS exit vers position grille
- MEMOTION TV: correction du padding de la grille pour couverture totale de la zone
- MEMOTION : phase READY affiche uniquement "PRÉPAREZ-VOUS" + barre d'équipes, sans la grille de cartes
- MEMOTION : footer des cartes équilibré — thème tronqué si long, étoiles et points clairement secondaires
- MEMOTION : effet de zoom sélection rendu visible grâce au LayoutGroup framer-motion (scale retiré de l'animation d'entrée des cartes)
- MEMOTION : fond opaque (#1a1a2e) sur la sous-phase REVEAL (gradient transparent remplacé)
- MEMOTION : bouton RÉVÉLER masqué tant que le timer est actif (admin)
- MEMOTION : animation zoom retour vers la grille après attribution des points (layoutId sur REVEAL)
- MEMOTION : affichage "PRÉPAREZ-VOUS" ajouté en phase READY (grille visible + animation)
- MEMOTION : mise en page des cartes dans la grille — image couvre la carte (object-fit: cover), thème en footer ; sans image, thème centré grand
- MEMOTION : effet zoom de sélection rendu visible — suppression du
transformStyle: preserve-3dqui bloquait l'animation layoutId framer-motion - MEMOTION : cartes QUESTION et REVEAL opaques pendant le flip — ajout background
#1a1a2esur fullscreen, suppression opacity des animations - MEMOTION : flip QUESTION→REVEAL coordonné (même direction que SELECTED→QUESTION)
- MEMOTION : retour à la grille animé par zoom inverse (suppression exit rotateY sur SELECTED, layoutId gère le reverse)
- MEMOTION : image/texte des cartes dans la grille agrandis — flex layout (image flex: 1, max-height 75%, thème plus grand, étoiles/points poussés en bas)
- MEMOTION : animation flip 3D coordonnée SELECTED → QUESTION (exit rotateY:90 + enter rotateY:-90 dans AnimatePresence partagée)
- MEMOTION : image plein écran sous-phase SELECTED agrandie à 70% ; fallback texte si pas d'image
- [MEMOTION — subphase SELECTED] : Nouveau step entre la sélection et la question
MEMOTION_SELECT→ subphaseSELECTED(carte zoomée en plein écran, RECTO visible, pas de timer)MEMOTION_FLIP(nouvelle action) → subphaseQUESTION+ démarrage timer per-carteMEMOTION_STOP_TIMER(nouvelle action) → arrêt manuel du timer, subphase resteQUESTION- Admin : panneau SELECTED avec boutons "DÉMARRER" (FLIP) et "ANNULER" ; bouton "STOP TIMER" en phase QUESTION
- TV : animation zoom framer-motion
layoutIddepuis la grille vers fullscreen (SELECTED) → flip rotateY (QUESTION/REVEAL) - Annulation depuis SELECTED :
MEMOTION_DONEvide ramène la carte àUNPLAYED
DoneMotionCardcancel-from-SELECTED utilise désormaise.state.MotionSelected(authoritative serveur) au lieu ducardIDclient
- [Nouveau type de jeu MEMOTION] : Grille de cartes à 3 faces (RECTO / VERSO / REVEAL)
- RECTO : thème + image optionnelle + difficulté (1★/2★★/3★★★ → 1pt/3pt/5pt)
- VERSO : question (texte + image) affichée en plein écran
- REVEAL : réponse (texte + image) affichée en plein écran
- Flow : Admin sélectionne carte → timer per-carte → Admin clique RÉVÉLER → attribution points → carte retournée avec couleur équipe
- 3 modes de jeu : SOLO, CHACUN_SON_TOUR, TANT_QUE_JE_GAGNE (mêmes règles que MEMORY)
- [#XX WebSocket Actions] : 4 nouvelles actions pour MEMOTION
MEMOTION_SELECT→{ CARD_ID: string }— sélectionne une carteMEMOTION_REVEAL→{}— révèle la réponseMEMOTION_DONE→{ CARD_ID: string, WINNER_TEAM?: string }— marque la carte comme jouéeMEMOTION_SET_TEAMS→{ TEAMS: string[] }— configure les équipes participantes
- [GameState enrichi pour MEMOTION] : 7 nouveaux champs
MEMOTION_SUBPHASE: sous-phase du jeu MEMOTION (GRID, QUESTION, REVEAL, DONE)MEMOTION_SELECTED: ID de la carte sélectionnéeMEMOTION_CARD_STATES: états des cartes (map CARD_ID → state)MEMOTION_CARD_TEAMS: assignation équipes par carte (map CARD_ID → TEAM)MEMOTION_CURRENT_TEAM: équipe actuelle en mode CHACUN_SON_TOURMEMOTION_PARTICIPATING_TEAMS: liste des équipes participantesMEMOTION_CURRENT_TEAM_COLOR: couleur RGB de l'équipe actuelle
- [Interface Admin pour MEMOTION] : Zone MEMOTION dans page Quiz
- Upload images cartes (RECTO + VERSO + REVEAL par carte)
- Édition grille (sélection cartes, ordre, difficultés)
- Prévisualisation RECTO/VERSO/REVEAL
- [Interface TV pour MEMOTION] : Affichage STATIQUE per-subphase
- GRID : grille de cartes avec images RECTO
- QUESTION : question VERSO (100vw×100vh)
- REVEAL : réponse REVEAL (100vw×100vh)
- DONE : carte retournée avec couleur équipe
Backend :
server-go/internal/game/models.go: structMotionCard, champsGameState.MEMOTION_*server-go/internal/game/engine.go: logic MEMOTION_SELECT/REVEAL/DONE, timer per-carteserver-go/internal/protocol/messages.go: actionsMEMOTION_SELECT,MEMOTION_REVEAL,MEMOTION_DONE,MEMOTION_SET_TEAMSserver-go/cmd/server/main.go: handlers et broadcast MEMOTIONserver-go/internal/server/http.go: endpoints MEMOTION (CRUD cartes)
Frontend :
web/src/pages/GamePage.jsx: affichage équipes, timer per-carteweb/src/pages/QuizPage.jsx(ex-QuestionsPage) : Zone MEMOTION (CRUD cartes)web/src/pages/PlayerDisplay.jsx: écrans TV phase GRID/QUESTION/REVEALweb/src/components/QuestionCard.jsx: composant affichage MEMOTION cardweb/src/hooks/useWebSocket.js: handling actions MEMOTION_*
- Self-update : stratégie copy-and-launch :
performRestart()ne modifie plus jamais le binaire courant. Le nouveau binaire versionné est copié dans le répertoire courant, lancé (attend les ports via retry loop), puis l'ancien processus se termine. Suppression de la logique backup/restore. (#70)
- Port TCP occupé lors du self-update : le serveur TCP (port 1234) échouait immédiatement au démarrage si l'ancien processus tenait encore le port, causant un crash fatal. Fix : retry loop 500ms dans
TCPServer.Start(), identique au fix HTTP du port 80 (#69)
- Panic au démarrage si config.json absent :
AckManager.Start()appelaittime.NewTicker(0)quandconfig.jsonétait introuvable, causant un crash immédiat. Fix : normalisation dansNewAckManager()(valeur ≤ 0 → 2000 ms) + fallbackconfig.Get()initialisé avecAckTimeoutMs: 2000, AckMaxRetries: 3(#68)
- Port HTTP occupé au démarrage lors d'un self-update : le nouveau processus retente désormais la liaison toutes les 500 ms (avec log d'avertissement) jusqu'à ce que le port soit libéré par l'ancien processus, au lieu d'échouer silencieusement (
server-go/internal/server/http.go)
- Fonds d'écran NEW_GAME multi-images : système de rotation automatique pour l'écran TV phase NEW_GAME
- Upload multiple d'images dans la Zone Quiz de la page
/admin/quiz(formats: jpg/png/gif/webp/svg) - Configuration par image : durée d'affichage (ms), opacité (0-100%)
- Rotation client-side côté TV :
setTimeoutbasé sur la durée de chaque image - Overlay absolu (z-index: 0) indépendant de l'opacité du texte et effets (z-index: 1)
- Fallback automatique : dégradé animé multicolore (violet→bleu→cyan→rose→ambre, 9s infini) si aucune image
- Persistance : stockage dans
data/files/new-game-backgrounds/+backgrounds.json - API REST :
POST /new-game-backgrounds(upload),PUT /new-game-backgrounds(config ordre/durée/opacité),DELETE /new-game-backgrounds?file=xxx(suppression) - Broadcast CONFIG_UPDATE : champ
new_game_backgrounds: []Background(jamaisomitempty— toujours présent)
- Upload multiple d'images dans la Zone Quiz de la page
- Écran TV NEW_GAME amélioré : nouveau système visuel de fonds d'écran multi-images avec fallback dégradé
- Titre dynamique (clamp 4rem→8vw), weight 900, text-shadow pour lisibilité
- Étoiles scintillantes (mix-blend-mode: screen)
- Soutien pour images dynamiques ou dégradé statique selon configuration admin
- CONFIG_UPDATE sérialisation : champ
new_game_backgroundsnonomitemptypour éviter que l'UI admin manque les mises à jour lors de suppression (v4.0.4)- Correction du bug qui empêchait la TV et l'UI admin de se mettre à jour lorsque l'utilisateur supprimait toutes les images
- Closes #66 (NEW_GAME multi-image backgrounds)
- Closes #67 (fonds d'écran dynamiques)
- NOUVELLE PARTIE : bouton dans GamePage (phase STOPPED uniquement) → reset complet (scores, historique, questions) + phase
NEW_GAME - Écran TV "NOUVELLE PARTIE À VENIR" : affichage plein écran statique sur
/tvpendant la phaseNEW_GAME, avec nom du quiz, thème et texte libre - Métadonnées de quiz : champs Nom du quiz / Thème général / Texte libre stockés dans le GameState et inclus dans les sauvegardes/restaurations
- Action
NEW_GAME(WebSocket) : déclencheInitGame()— reset scores teams+joueurs, historique, statuts questions, question sélectionnée - Action
UPDATE_QUIZ_META(WebSocket) : met à jour les métadonnées du quiz en temps réel - Phase
NEW_GAME: nouvelle phase de machine d'état — transitoire jusqu'à la sélection de la première question
- Menu "Questions" → "Quiz" : label Navbar et URL
/admin/quiz - Page Quiz (ex-QuestionsPage) réorganisée en 3 zones : Quiz (métadonnées) / Ambiance (fonds d'écran) / Questions (liste)
- Closes #66 — Bouton Start GAME
- Closes #67 — Change QUESTIONS en QUIZ
- [#11 Backend]: Endpoints WebSocket dédiés
/ws/admin,/ws/tv,/ws/player— filtrage intelligent des messages par type de client- Trois endpoints spécialisés remplacent le broadcast global — chaque client reçoit uniquement les messages pertinents à son rôle
- Table de filtrage : 25 actions distribuées selon le type client (Admin, TV, VPlayer)
- Exemples : UPDATE/START/STOP/PAUSE → Admin+TV+VPlayer ; QUESTIONS/CLIENTS → Admin only ; PLAYER_REJECTED → VPlayer only
- Rétrocompatibilité :
/wsconservé comme alias vers/ws/admin ClientTypedans le modèle de connexion WebSocket — enforcé au moment de la création (pas desetClientTypedynamique)
- [#41 Backend + Frontend]: Réduction payload WebSocket buzzers — whitelist d'actions
- Buzzer physiques reçoivent uniquement : UPDATE, UPDATE_TIMER, START, CONTINUE, STOP, PAUSE, READY, RESET, HELLO, LED_SET, OTA_UPDATE, WIFI_CONFIG (12 actions) — game state jamais transmis aux buzzers
- Nouveau helper
BroadcastIfRelevant()danswebsocket_buzzer.go— filtre intelligemment les payloads par type d'action - Réduit bande passante et latence WebSocket pour les buzzers
- [#54 Backend + Frontend + Firmware]: Protocole ACK buzzer — confirmation de réception des messages prioritaires
- Messages LED_SET, OTA_UPDATE, WIFI_CONFIG générés avec
MSG_IDunique (12-char hex, optionnel omitempty) - Firmware BuzzClick répond avec
ACKavant d'appliquer l'action (minimise latence de confirmation) - AckManager côté serveur : registre, retry configurable (
ack_timeout_ms,ack_max_retries) et expiry automatique - Nouveau champ
ACK_PENDINGdans le modèle Bumper — badge ⚠ horloge quand buzzer attend confirmation - Badge ACK_PENDING visible dans TeamCard et TeamsPage (style inline React, fond amber
#f59e0b, icône horloge)
- Messages LED_SET, OTA_UPDATE, WIFI_CONFIG générés avec
- [#11 Backend + Frontend]: Sérialiseurs payload différenciés par type de client
SerializeForAdmin(): payload complet (toutes les infos) pour l'admin webSerializeForWebClient(): payload réduit pour TV et VPlayer — élimine FIRMWARE_VERSION, IS_OUTDATED, OTA_STATUS, OTA_PERCENT, ACK_PENDING et infos serveur (config)SerializeForBuzzer(): payload minimal pour buzzers physiques (phase, timer, bumpers avec ID-NAME-TEAM-CONNECTED, teams avec NAME-COLOR-STATUS)BroadcastRawToTypes()danswebsocket.go— achemine les payloads sérialisés correctement selon le type client- Réduction de 40-60% du volume de données WebSocket pour TV/VPlayer
- [#11 Frontend]:
GameProvideraccepte un propendpointpour routing multi-rôleGameProvider({ endpoint='/ws/admin' })— transmis àuseWebSocket()pour ciblage de l'endpoint WS- Routes :
/admin/*→ défaut (/ws/admin) ;/tv→endpoint="/ws/tv";/playeret/enroll→endpoint="/ws/player" - Permet l'évolution future vers plusieurs instances GameProvider avec endpoints différents (ex: TV + admin simultanées dans iframe)
- [Firmware BuzzClick v3.8.0]: Implémentation ACK côté firmware
ws_sendAck()dansclick_websocket_espidf.h— envoi non-bloquant de réponses ACK- Handler MSG_ID dans
parseJSON()— ACK envoyé AVANT d'appliquer l'action (LED_SET, OTA_UPDATE, WIFI_CONFIG) - Version firmware alignée avec serveur : 3.8.0
- [#41 Backend]: Whitelist buzzer étendue pour support état du jeu partiel
- Whitelist précédente (v3.8.0 initial) : LED_SET, OTA_UPDATE, WIFI_CONFIG, HELLO (4 actions)
- Whitelist étendue : UPDATE, UPDATE_TIMER, START, CONTINUE, STOP, PAUSE, READY, RESET, HELLO, LED_SET, OTA_UPDATE, WIFI_CONFIG (12 actions)
- Permet aux buzzers physiques de recevoir et afficher l'état du jeu (phase, timer, listes équipes/joueurs) sans informations sensibles (config, firmware)
- Utilise
SerializeForBuzzer()pour la sérialisation minimale
- [#11 Backend]: Détails de routage WebSocket par endpoint
/ws/admin: Admin web client — reçoit ALL, UPDATE, START, STOP, PAUSE, CONTINUE, REVEAL, RESET, QCM_HINT, ENROLLMENT_UPDATE, READY, REMOTE, BACKGROUND_CHANGE, CONFIG_UPDATE, SHOW|HIDE_QR_CODE, FULL, MEMORY_*, QUESTIONS, CLIENTS, FIRMWARE_VERSION, PLAYER_CONNECTED, PLAYER_ASSIGNED/ws/tv: TV display client — reçoit UPDATE, START, STOP, PAUSE, CONTINUE, REVEAL, RESET, QCM_HINT, ENROLLMENT_UPDATE, READY, REMOTE, BACKGROUND_CHANGE, CONFIG_UPDATE, SHOW|HIDE_QR_CODE, FULL, MEMORY_* (serialized viaSerializeForWebClient())/ws/player: VPlayer web client — reçoit UPDATE, START, STOP, PAUSE, CONTINUE, REVEAL, RESET, QCM_HINT, ENROLLMENT_UPDATE, PLAYER_REJECTED, PLAYER_CONNECTED, PLAYER_ASSIGNED (serialized viaSerializeForWebClient())
- [#54 Frontend]: Migration des pages TV/VPlayer vers endpoints dédiés
PlayerDisplay.jsx(TV) : utilise/ws/tvau lieu de l'endpoint adminEnrollPage.jsx,VPlayerPage.jsx(VPlayer) : utilisent/ws/player— suppression dusetClientTypedynamiqueGameContext.jsx: spécifie explicitement/ws/admin— architecture à connexion unique par rôleGameProvideraccepte propendpointtransmis au hookuseWebSocket()
- [Frontend]: Filtres catégories dans la barre Équilibre (fixes #57)
- Les catégories dans la barre d'équilibre de
GamePagesont désormais cliquables pour filtrer les questions affichées - Multi-sélection : cumuler plusieurs catégories actives, clic re-sélectionne/désélectionne
- Les catégories dans la barre d'équilibre de
- [Backend + Firmware]: Couleurs LEDs buzzers sélectionnées par teinte (hue-based) pour cohérence palette (fixes #61)
- Nouveau helper
nearestPaletteColorByHue()— sélectionne la couleur palette la plus proche par distance de teinte HSL - Évite les mélanges visuellement incohérents sur les buzzers quand la couleur d'équipe est proche de plusieurs entrées palette
- Remplace le calcul RGB direct par une approche HSL plus fidèle à la perception humaine
- Nouveau helper
- [Firmware BuzzClick]: Race condition OTA —
performOTA()déplacé dans tâche FreeRTOS séparée- La fonction OTA bloquait la tâche WebSocket, provoquant une déconnexion à ~20% du téléchargement et un échec silencieux
- Fix :
performOTA()exécuté dansota_task(FreeRTOS, 16 KB stack) — la connexion WebSocket reste active pendant le téléchargement et le flash
- [Firmware BuzzClick]: Stack overflow
websocket_task— taille augmentée 4096 → 8192 bytes- Stack insuffisant provoquait des crashes aléatoires lors de la réception de messages WebSocket de taille importante
- [Firmware BuzzClick]: VERSION dans
platformio.inicorrigée de 3.6.6 → 3.7.0- Le firmware était compilé avec un numéro de version incorrect — le badge firmware sur l'interface admin affichait une version erronée après flash
- [Frontend]: Badge version cliquable dans la Navbar — redirige vers
/admin/updates(fixes #63)- Le numéro de version affiché dans la navbar est désormais un lien direct vers la page des mises à jour
- Permet un accès rapide depuis n'importe quelle page
- [Frontend]: Page d'enrollment VJoueur améliorée — double QR code et barre de progression (fixes #43)
- Deux QR codes côte à côte sur la vue TV d'inscription (
/tven phase ENROLLMENT) :- QR code WiFi : permet aux joueurs de rejoindre le bon réseau avant de s'inscrire
- QR code VJoueur : URL d'inscription avec port correct (
window.location.host)
- Barre de progression en temps réel : nombre de joueurs inscrits / max visible sur l'affichage TV
- Deux QR codes côte à côte sur la vue TV d'inscription (
- [Frontend]: QR codes TV d'enrollment redessinés pour projection (fixes #51)
- Affichage redessiné avec meilleure lisibilité, contraste optimisé pour projection TV grande distance
- [Frontend]: Filtres questions par catégories multi-sélection dans la barre Équilibre (fixes #40)
- Sur
GamePage, la barre d'équilibre des catégories permet de filtrer les questions visibles par catégorie - Indicateur visuel sur les catégories actives (surlignage coloré)
- Sur
- [Backend]: Noms de fichiers de sauvegarde versionnés avec horodatage (fixes #44)
- Les archives téléchargées incluent désormais la version serveur et la date dans leur nom
- Format :
buzzcontrol-full-backup-v3.7.0_2026-04-26.tar(complet) etbuzzcontrol-backup-v3.7.0_2026-04-26.tar(sélectif) - Facilite la gestion et l'identification temporelle de multiples sauvegardes
- [Backend + Firmware]: Animation COMET lors de l'attribution de points — couleur dynamique (fixes #50)
- Nouvelle action serveur
sendLEDSetComet: envoieEFFECT: "COMET"avec champCOMET_COLORcalculé dynamiquement COMET_COLOR: serveur choisit or ([255,215,0]) ou blanc ([255,255,255]) selon le contraste avec la couleur d'équipe (dist² euclidien < 8000 → blanc pour lisibilité)- Firmware : state machine
manageLedComet()— bande rotative 23 LEDs APA102, 2 tours (~3.3 s), utilisecometR/G/Bdu payload (plus de gold hardcodé) - Déclenchée automatiquement à chaque attribution de points (
TEAM_POINTS,BUMPER_POINTS) - Retour automatique à l'état LED précédent après l'animation
- Nouvelle action serveur
- [Backend]: Les équipes vides (sans buzzer assigné) ne bloquent plus le passage en READY/START
AreAllTeamsReady()ignorait toutes les équipes sans exception — une équipe sans buzzer (Ready=falsepermanent) empêchait leSTARTmême si toutes les équipes actives étaient prêtes- Refactoring : nouveau helper privé
getActiveTeams()— retourne uniquement les équipes ayant ≥1 buzzer assigné AreAllTeamsReady(),updateTeamsReady()et l'auto-init Memory utilisent désormaisgetActiveTeams()— cohérence avec le filtre d'affichage frontend (buzzers.length > 0)- Test de non-régression :
TestEngine_AreAllTeamsReady_EmptyTeamsIgnored
- [Backend]: Détection déconnexion WebSocket réduite à ≤ 5 s (buzzers physiques et clients admin)
pingPeriod: 30 s → 3 s ;ReadDeadline(pongWait) : 25 s / 60 s → 5 s ;WriteDeadline: 10 s → 3 s- Les buzzers qui perdent la connexion sans TCP FIN (coupure alimentation, chute WiFi) sont détectés en ≤ 5 s
- Formule : pongWait (5 s) > pingPeriod (3 s) garantit un ping envoyé avant l'expiration — aucun faux positif
- Appliqué sur
websocket_buzzer.go(buzzers physiques) etwebsocket.go(clients admin/TV)
- [Backend]: Reconnexion rapide (< 5 s) transparente — aucun flash de badge
- Race condition : si un buzzer reconnectait avant que le serveur détecte la déconnexion de l'ancienne connexion, le callback
OnBuzzerDisconnectedde la connexion zombie posaitCONNECTED=falseaprès la reconnexion - Fix :
OnBuzzerDisconnectedvérifieIsClientConnected(mac)avant d'agir — ignore le timeout de la connexion zombie si une nouvelle connexion active existe pour ce MAC
- Race condition : si un buzzer reconnectait avant que le serveur détecte la déconnexion de l'ancienne connexion, le callback
- [Frontend]: Badge déconnexion buzzer en style inline (cercle jaune + icône wifi-off SVG)
- Remplacement de la classe CSS
.buzzer-disconnected-badgepar des styles React inline (background:#f59e0b,stroke:white) pour garantir le rendu indépendamment du chargement CSS - Appliqué sur
TeamCard.jsxetTeamsPage.jsx(2 emplacements : buzzers assignés + non assignés)
- Remplacement de la classe CSS
- [Frontend]: Badge ⚠ buzzer déconnecté étendu à la page Équipes (
TeamsPage.jsx)- Affiché dans les rows membres (draggable) et dans la section buzzers non assignés
- Condition stricte identique à TeamCard :
CONNECTED === false && !IS_VIRTUAL && !IS_VPLAYER - 7 tests Vitest/RTL ajoutés dans
TeamsPage.test.jsx(dont cas buzzer firmware pre-v3.6.6 sans champ CONNECTED)
- [Backend]: Champ
CONNECTEDpersisté sur disque — buzzers affichés comme connectés après redémarrage serveur (fixes #48)CONNECTED=trueétait sérialisé dans le JSON de persistance et rechargé au démarrage — les buzzers apparaissaient connectés sur TeamCard même sans connexion physique active- Fix : au démarrage du moteur (
NewEngine), tous les bumpers chargés depuis le disque ont leur champCONNECTEDexplicitement réinitialisé àfalseavant d'accepter des connexions - Test de non-régression :
TestEngine_StartupConnectedReset
- [Backend + Frontend]: Indicateur de déconnexion buzzer en temps réel — badge ⚠ sur TeamCard (fixes #48)
- Nouveau champ
CONNECTED booldans le modèleBumper(sansomitempty—falsetoujours sérialisé en JSON) handleHello(): positionneCONNECTED=trueà la connexion WebSocketOnBuzzerDisconnectedcallback dansBuzzerWebSocketHub: positionneCONNECTED=falseà la déconnexion et broadcastUPDATEUpdateBumper()dans le moteur : gère le casCONNECTEDpour propager la mise à jour d'état- Frontend
TeamCard.jsx: badge ⚠ jaune inline dans la row du buzzer déconnecté (conditions strictes :!isVPlayer && !isVirtual && connected === false) - Tests de non-régression :
TestEngine_UpdateBumper_Connected - Au redémarrage serveur : tous les buzzers chargés depuis le disque ont
CONNECTED=false→ badge ⚠ affiché jusqu'à réception du HELLO (fix introduit en v3.6.7)
- Nouveau champ
- [Backend]: Mode haute-fréquence UDP broadcaster quand un buzzer est déconnecté (fixes #64)
SetHighFrequency()dansBroadcasterManager: active un intervalle de 500 ms (vs 5 s normal) pour accélérer la redécouverte serveur par le buzzer déconnecté- Priorité des intervalles : enrollment (1 s) > haute-fréquence (500 ms) > normal (5 s)
updateBroadcasterFrequency()dansmain.go: orchestre le passage en haute-fréquence si au moins un buzzer physique est déconnecté, retour au mode normal quand tous sont reconnectés- Appelé aux points clés :
handleHello(),OnBuzzerDisconnected,handleDeleteBumper(),handleFullUpdate(), démarrage serveur
- [Firmware BuzzClick]: Spinner
WS_RECONNECTINGgelé pendant le teardown WebSocket (ws_safe_destroy)ws_safe_destroy()bloquait jusqu'à 700 ms (close timeout 500 ms + delay 200 ms) — le spinner était figé pendant toute la durée du teardown- Fix : close timeout réduit à 0 ms (non-bloquant, fire-and-forget — le pair peut être injoignable), delay réduit à 50 ms, appel
manageLedError()avant le delay pour maintenir l'animation pendant le teardown
- [Firmware BuzzClick]: Spinner
WS_RECONNECTINGgelé ~4 s pendantesp_websocket_client_stop()(suite)- Cause racine :
stop()bloque jusqu'à l'arrêt complet de la tâche réseau interne du SDK (~4 s quand le serveur est injoignable) —manageLedError()ne tourne plus pendant ce temps - Fix :
stop()+destroy()déchargés sur une tâche FreeRTOS temporaire (ws_destroy_task, 4 KB stack, priorité 5) ;loop()entre dans une boucle 100 ms tick —manageLedError()+esp_task_wdt_reset()— jusqu'à completion ;wsGenerationincrémenté etwsClient=NULLavant le spawn pour invalider tout callback stale network_timeout_msrevert : champ inexistant dans cette version du SDK ESP32-C3 (seulpingpong_timeout_secest disponible) — le freeze est désormais résolu côté tâche FreeRTOS, sans modifier les timeouts SDK
- Cause racine :
- [Firmware BuzzClick]: Patterns LED
manageLedBlink()etupdateGrayRotation()non isolés pendant OTA- Ces fonctions appelées dans
loop()pouvaient écraser le blue-blink OTA ou le patternOTA_ERRORmalgré le guardotaInProgressdéjà en place surLED_SET - Fix : guard
if (otaInProgress) return;ajouté dansupdateGrayRotation()etmanageLedBlink()— isolation OTA LED complète (fixes #62 suite)
- Ces fonctions appelées dans
- [Firmware BuzzClick]:
volatilemanquant surotaInProgress— conflit de déclarationexternotaInProgressétait défini sansvolatiledansclick_otaManager.hmais l'externdansclick_serverConnection.hattendaitvolatile bool→ erreur de compilation (conflicting-declaration)- Fix :
volatile bool otaInProgressdans les deux fichiers, aligné sur la conventionwsConnected/wsConnecting/wsGeneration
- [Firmware BuzzClick]: Reconnexion WebSocket avec intervalle fixe 10 s après déconnexion
checkWebSocketConnection()attendait systématiquement 10 s entre chaque tentative — l'utilisateur voyait le spinner tourner pendant 10 s sans tentative de reconnexion- Fix — machine à 3 états :
- IMMEDIATE (
wsDisconnectedImmediate=true) : tente une reconnexion dès le prochain tickloop()aprèsDISCONNECTED/ERROR - WAIT_UDP (
wsWaitingForBroadcast=true) : si la tentative immédiate échoue, attend un heartbeat UDP du broadcaster serveur ; essaie chaque IP reçue dans l'ordre - IDLE :
wsConnected=true, les deux flags sont àfalse
- IMMEDIATE (
WiFiGotIP()(reconnect après coupure WiFi) resetwsWaitingForBroadcast=false+wsDisconnectedImmediate=truepour re-entrer en état IMMEDIATE
- [Firmware BuzzClick]: Race condition au boot —
esp_websocket_client_start()retournait ESP_FAIL (-1) sur l'ESP32-C3 (fixes #59)- Deux tâches FreeRTOS concurrentes (
WiFiGotIPevent task etloop()) pouvaient appelerconnectWebSocketoucheckWebSocketConnectionsimultanément, provoquant un doublestart()sur le même handle - Ajout du flag
volatile bool wsConnecting: positionné àtrueà l'entrée deconnectWebSocket(), remis àfalsesur toutes les branches de sortie checkWebSocketConnection()retourne immédiatement siwsConnecting == true
- Deux tâches FreeRTOS concurrentes (
- [Firmware BuzzClick]: Reconnexion WebSocket bloquée après timeout (fixes #42)
- Après un timeout,
wsClientétait mis àNULLparws_safe_destroy()—checkWebSocketConnection()ne relançait plus jamais de tentative (conditionwsClient != NULL) - La condition est remplacée par
!wsServerIP.isEmpty(): reconnexion infinie tant que le serveur est connu
- Après un timeout,
- [Firmware BuzzClick]: Régressions LED team color et spinner WS_RECONNECTING invisible causées par réécriture complète du buffer (commit 8ea9614)
- La réécriture complète écrasait la couleur d'équipe posée par
LED_SETà chaque tick du spinner - Corrigé par delta-update : seuls 2 pixels modifiés par tick via
applyLedColorToBuffer()— fond de couleur intégralement préservé
- La réécriture complète écrasait la couleur d'équipe posée par
- [Firmware BuzzClick]: Spinner WS_RECONNECTING gelé pendant la boucle de reconnexion
manageLedError()n'était appelé que dansloop(), maisconnectWebSocket()peut bloquer jusqu'à 10 smanageLedError()est désormais appelé dans la boucle d'attente interne deconnectWebSocket()— animation maintenue pendant toute la tentative
- [Firmware BuzzClick]: Thread-safety FreeRTOS — variables LED cross-task déclarées
volatile- Variables
currentRed,currentGreen,currentBlue,currentIntensity,g_ledErrorStepIdx,g_ledErrorLastSteprenduesvolatilepour garantir la cohérence entre les tâches FreeRTOS (event task +loop())
- Variables
- [Firmware BuzzClick]: Replay des phases boot LED sur reconnexion WiFi (ORANGE 1/4 et 2/4)
WiFiGotIP()rejouait les phases LED de boot siwsServerIPétait déjà connu (reconnexion après coupure WiFi)- Ajout d'un early return dans
WiFiGotIP()siwsServerIPn'est pas vide — évite l'interruption du spinner WS_RECONNECTING
- [Firmware BuzzClick]: Commandes
LED_SETserveur appliquées pendant une mise à jour OTA (fixes #62)- Les messages
LED_SETreçus pendant l'OTA écrasaient les patterns d'avancement — risque d'état LED incohérent - Guard
otaInProgressajouté dans le handlerLED_SETdeparseJSON(): les messages sont ignorés si une OTA est en cours
- Les messages
- [Tests Go]: Alignement
updater_test.goavec la signatureNewUpdater(version, dataDir)(fixes #44)- Les tests appelaient l'ancienne signature sans
dataDir, provoquant des erreurs de compilation - Tests mis à jour pour passer le répertoire de données en second argument
- Les tests appelaient l'ancienne signature sans
- [Firmware BuzzClick]: Indicateur visuel de reconnexion WebSocket non-intrusif (fixes #42)
- Nouveau pattern LED
WS_RECONNECTING: 1 pixel blanc se déplace sur l'anneau (23 LEDs × 100ms = ~2.3s/tour) - Delta-update : seuls 2 pixels modifiés par tick (restauration du pixel précédent au fond + avance du pixel blanc) via
applyLedColorToBuffer() - Pattern
[bg][WHITE][bg]— pas de voisins éteints, fond de couleur préservé intégralement - Déclenché immédiatement sur
WEBSOCKET_EVENT_DISCONNECTED/WEBSOCKET_EVENT_ERROR— pas d'étape rouge fixe intermédiaire - La couleur d'équipe ou de réponse reste visible sur toutes les autres LEDs (overlay non-intrusif)
- Au retour du serveur :
clearLedError()+resendLEDOnReconnect()restaure l'état complet
- Nouveau pattern LED
- [Tests Go]: Tests de non-régression pour la détection merged binary et le serving OTA (commit 1b067e9)
IsMergedBinary(): 7 cas (magic présent/absent, longueurs limites, nil)GetAppFirmware(): identité app-only, extraction merged à 0x10000, erreur merged-too-smallSaveFirmware(): merged ≤ 3 MB accepté, rejet au-delà deFirmwareMaxSizehandleAPIFirmwareDownload: app-only servie intacte, merged sert uniquement l'app ;Content-Lengthannonce la taille app-only pour OTA- Endpoint
merged.bin: 404 pour app-only, binaire complet pour merged ; flagIS_MERGEDdans/api/firmware/buzzclick/version
- [UpdatePage]: Persistance des téléchargements entre redémarrages (fixes #44)
- Les binaires téléchargés sont stockés dans
data/updates/avec leur numéro de version (ex:buzzcontrol-v3.6.3-windows-amd64.exe) au lieu du dossier temp OS - L'API
/api/updatesretournedownloaded: true+local_pathsi le binaire est déjà présent localement - Badge "✓ Téléchargé" affiché sur les versions déjà présentes — le bouton "Appliquer" est disponible sans re-téléchargement
- Les binaires téléchargés sont stockés dans
- [CI/CD + Firmware]: Le firmware publié en release et embarqué dans l'exe était app-only, empêchant le flash USB direct via esptool
- Le pipeline génère désormais un merged binary (bootloader + partitions + boot_app0 + app) via
esptool merge_bin - Le serveur auto-détecte le format et extrait l'app-only pour l'OTA (transparent pour les buzzers)
- Fix SIZE mismatch dans
OTA_UPDATE: le serveur annoncait la taille du merged mais servait l'app-only, causant un abandon OTA firmware-side
- Le pipeline génère désormais un merged binary (bootloader + partitions + boot_app0 + app) via
- [Firmware BuzzClick]: OTA échoue quand le serveur tourne sur un port non-standard (fixes #50)
- L'URL OTA était construite avec le port 80 en dur (
http://IP/api/firmware/...), causant un échec HTTP quand le serveur écoute sur un autre port (ex: 8080) - Le port est désormais pris depuis la connexion active (
serverIP+localUdpPortpositionnés partryConnectToServer()) — couvre les trois chemins de découverte : UDP broadcast heartbeat, fallback NVS, et mDNS - Chaîne de fallback : port de la connexion active →
server_tcp_portNVS → URL du messageOTA_UPDATE(rétrocompatibilité)
- L'URL OTA était construite avec le port 80 en dur (
- [UpdatePage]: Boutons Télécharger et Appliquer non fonctionnels (fixes #44)
- CORS corrigé : suppression du header
Access-Control-Allow-Credentials: trueincompatible avec les origines wildcard - Codes HTTP sémantiques sur les endpoints
/api/updates/*(400/404/503/500) au lieu de 200 systématique - Support de la variable d'environnement
GITHUB_TOKENpour éviter le rate limiting de l'API GitHub - Remplacement atomique du binaire Windows via rename (évite le verrou OS sur l'exécutable en cours)
- Proxy Vite
/apiajouté pour le mode développement (vite.config.js) - Validation anti path-traversal renforcée dans
HandleApplyUpdate - Bouton "Appliquer" désactivé pendant le chargement pour prévenir les double-clics
- CORS corrigé : suppression du header
- [GamePage]: Les équipes sans joueur sont masquées sur
/admin/game(fixes #45)
- [Firmware BuzzClick]: Différenciation visuelle des états d'erreur LED (fixes #49)
- WiFi échec → rouge clignotant lent (1 Hz)
- WebSocket déconnectée → rouge clignotant rapide (4 Hz)
- WebSocket timeout définitif → rouge pulsant lent
- OTA erreur → rouge + flash blanc
- Nouveau module
click_ledErrorPatterns.h— state machine non-bloquante
- [Firmware BuzzClick]:
CORE_DEBUG_LEVELpassé de 4 (DEBUG) à 3 (INFO) — les logs mémoire WATCHDOG ne sont plus compilés en production, réduisant l'overhead (fixes #46) - [Firmware BuzzClick]: Logs Broadcaster enrichis avec l'IP source du heartbeat reçu et l'IP locale d'écoute — facilite le diagnostic de découverte serveur (fixes #47)
- [Firmware BuzzClick]: Instruction access fault (MCAUSE=0x1, MEPC=0x00010000) après timeout WebSocket dans la transition NVS→broadcast
- Generation counter sur les event handlers ESP-IDF pour invalider les callbacks stale après
destroy() volatilesurwsClient/wsConnected/wsGenerationpour visibilité cross-task FreeRTOS- Graceful close (
esp_websocket_client_close()+ drain 200ms) avantdestroy() - Fix resource leak si
esp_websocket_client_start()échoue - Defense-in-depth dans
click_WifiManager.h(ré-assertion NULL + delay 100ms avant retry)
- Generation counter sur les event handlers ESP-IDF pour invalider les callbacks stale après
- [Game/WebSocket]: Suppression d'un buzzer non reflétée dans l'UI en temps réel
- Cause :
GameData.BumpersetGameData.Teamsavaient le tagomitempty— une map vide{}était omise du JSON broadcasté, empêchant le frontend de recevoir la mise à jour - Fix backend : suppression de
omitemptysurTeamsetBumpersdansGameData, nil-guard dansGetGameJSON()(models.go,engine.go) - Fix frontend : checks d'existence explicites dans les handlers WebSocket
UPDATE,BUMPERetREMOTE(useWebSocket.js)
- Cause :
- [ConfigPage]: Le champ IP Serveur dans la page Config n'est plus pré-rempli avec le hostname du navigateur (
window.location.hostname). Initialisé à vide (''), il est renseigné uniquement si une IP est sauvegardée côté serveur. Depuis v3.2.0, l'IP est optionnelle (découverte automatique via UDP broadcast) — un hostname transmis au firmware causait l'erreur "ERROR:Invalid IP format" dans le handler AT. (ConfigPage.jsx, commiteb8b635) - [Firmware BuzzClick]: Correction d'un crash Guru Meditation Error (Load access fault) survenant lors d'un timeout de connexion WebSocket.
esp_websocket_client_destroy()était appelé sansesp_websocket_client_stop()préalable, laissant la tâche interne ESP-IDF accéder à un handle détruit. Ajout dustop()avantdestroy()dans le path timeout et nettoyage défensif de tout client existant en début deconnectWebSocket(). (click_websocket_espidf.h, commitf0bd596)
- [BuzzClick/OTA]: Handler
OTA_UPDATE— fallback sur l'URL du message siserver_ipNVS est vide (cas d'un flash USB complet qui efface le NVS), évite l'échec silencieux de l'OTA après première configuration - [Server/resolveServerIP]: Ajout de logs diagnostics (
LogInfo) pour tracer l'IP retournée parGetServerIPs()— facilite le débogage des problèmes d'IP serveur envoyée aux buzzers - [ConfigPage]:
wifiServerIpinitialisé àwindow.location.hostnameau lieu de''— l'IP serveur dansUSBConfigModalest désormais correcte dès l'ouverture même siconfig.jsonne contient pas de valeur - [TeamCard]: Ctrl+clic sur le badge firmware ouvre la modale OTA même si
IS_OUTDATED=false— permet de forcer un reflash sur un buzzer à jour - [TeamsPage]: Même correctif Ctrl+clic que
TeamCardpour cohérence entre les deux vues buzzer
- [Server/WIFI_CONFIG]:
resolveServerIP()utilise désormaisserver.GetServerIPs()(IP dynamique) au lieu decfg.ServerIP(valeur statique config.json) — les buzzers reçoivent l'IP réelle du serveur - [ConfigPage/USBConfigModal]: Ajout des props
serverIpetserverPortmanquantes danswifiConfig— affichait "(non défini)" au lieu des valeurs réelles - [TeamCard]: Ctrl+clic sur le badge firmware ouvre la modale OTA même si le buzzer n'est pas marqué
IS_OUTDATED - [TeamsPage]: Même correctif Ctrl+clic que TeamCard pour cohérence entre les deux vues
- [GamePage]: Bouton "à suivre" affichant la prochaine question non jouée dans le bandeau admin (fixes #39)
- Format :
à suivre : #ID | catégorie | TYPE | titre— à droite du badge d'état - Visible dans toutes les phases actives, avec deux comportements selon la phase :
STOPPED/REVEALED/PREPARE/READY: opacité 100%, cliquableSTARTED/PAUSED/COUNTDOWN/ENROLL: opacité 50%, non cliquable
- Logique de sélection : première question après la courante avec STATUS différent de
STOPPED,REVEALED,PLAYED - Au clic : sélectionne directement la prochaine question non jouée
- Format :
- [GamePage]: Badges de phase
COUNTDOWNetENROLLajoutés dans le bandeau admin
- [GamePage]: Opacité 50% sur les questions non jouées dans la liste admin pendant les phases
STARTED/PAUSED/COUNTDOWN/ENROLL- La question courante et les questions jouées restent à pleine opacité
- [GamePage/Memory]: Sélecteur d'équipes Memory visible en phase PREPARE/READY pour les questions Memory
- Layout en ligne : équipes sélectionnées à gauche, séparateur
|, équipes disponibles à droite - Mode SOLO (
MEMORY_MODEvide ou"SOLO") : sélection d'une seule équipe à la fois, chip active colorée sans ×, chips disponibles avec + - Mode
CHACUN_SON_TOUR: label "Chacun son tour", sélection multiple ordonnée avec numéros et × - Mode
TANT_QUE_JE_GAGNE: label "Tant que je gagne", sélection multiple ordonnée avec numéros et ×
- Layout en ligne : équipes sélectionnées à gauche, séparateur
- [GamePage/Memory]: Correction du bug d'invisibilité du sélecteur quand
MEMORY_MODEest absent du payload (omitempty) — la valeur vide est désormais traitée comme"SOLO"
- [LED/BuzzState]: Implementation correcte des machines a etat LED selon
docs/BUZZER_LED_STATE_MACHINE.md- Ajout du type
BuzzState(NONE/MOI/EQUIPE/AUTRE) dansgame/models.go - Tracking
bumperBuzzStatepar buzzer dansApp— reset au READY, mis a jour a chaque buzz - Filtre BUTTON : les messages
BUTTONsont desormais silencieusement ignores en dehors de la phaseSTARTED(READY, COUNTDOWN, PAUSED, REVEALED, STOPPED) - NORMAL : STARTED/PAUSED/REVEALED : MOI=BLINK 100%, EQUIPE=SOLID 100%, NONE/AUTRE=DIM 25%
- QCM : STARTED/PAUSED : MOI/EQUIPE=couleur equipe SOLID 100% (cache la reponse), AUTRE/NONE=couleur reponse SOLID 100% ; REVEALED : correct+1er=BLINK, correct+pas1er=SOLID 100%, mauvais/non-buzze=DIM 25%
- MEMORY SOLO : STARTED actif=SOLID 100%, inactif=DIM 25% ; PAUSED toutes=DIM 25%
- MEMORY multi-equipes : STARTED actif=SOLID 100%, prochain=SOLID 50% (INTENSITY=128), autres participants=DIM 25%, non-selectionnes=OFF ; PAUSED toutes=DIM 25%
broadcastContinueenvoie desormais unLED_SETapres reprise de jeu (etait manquant)- Les intensites sont uniformisees : DIM 25% = intensity 64 (25% de 255)
- Ajout du type
- Remplacement des fonctions LED monolithiques par une architecture par type de jeu (
sendLEDSetForBuzzerNormal/QCM/Memory) isFirstBuzzTeam: determine si une equipe est la premiere a avoir buzze via les timestamps enginesendLEDSetForBuzzerQCMReveal: gestion precise BLINK/SOLID/DIM au REVEALED QCMnextMemoryTeam: calcul de l'equipe suivante dans la rotation multi-equipes- 18 nouveaux tests unitaires couvrant toutes les transitions LED
- [LED/Server-Driven]: Refonte complete du pilotage LED — approche server-driven avec action
LED_SET- Le serveur calcule et envoie l'etat LED exact (couleur, intensite, effet) a chaque changement d'etat pertinent
- Le firmware applique simplement ce qu'il recoit — aucune logique LED locale cote buzzer
- Nouveau payload
LED_SET:{"COLOR": [R,G,B], "INTENSITY": 0-255, "EFFECT": "SOLID"|"BLINK"|"DIM"} - Suppression des 4 actions QCM-LED precedentes (
QCM_COLOR,QCM_DIM,QCM_REVEAL,QCM_RESET) - Suppression de
manageLeds()et de toute la machine d'etat LED cote firmware bumperLEDState: le serveur memorise le dernier etat LED par buzzer pour le reenvoyer au reconnect (HELLO)- Couverture : QCM READY/START (couleur reponse SOLID 100%), PAUSE (DIM 25% QCM / 100% buzzer actif + 2% autres en NORMAL), STOP (couleur equipe SOLID 100%), REVEALED (BLINK correct / SOLID wrong / DIM 10% non-buzze), Memory (actif 100% / inactif 25%)
- Correction du flash visible a chaque
UPDATE_TIMER:handleUpdateAction()ne modifie plus les LEDs
- [Protocol]: Remplacement de
ActionQCMColor/QCMDim/QCMReveal/QCMResetparActionLEDSet = "LED_SET" - [Firmware BuzzClick]:
manageLedQCMBlink()→manageLedBlink()(generique, pilote par LED_SET EFFECT=BLINK)
- [Updater]: Correction du seuil
MinBinarySizeet du timeout de telechargement (fixes #38)- Le seuil de taille minimale du binaire etait trop eleve, rejetant des binaires valides lors de la mise a jour automatique
- Le timeout de telechargement etait trop court pour les connexions lentes, causant des echecs d'update
- [QCM/Scoring]: Correction du calcul de penalite QCM — la penalite est desormais basee sur
HINTS_AT_BUZZdu buzzer individuel (nombre d'indices reveles au moment precis du buzz), et non sur le nombre d'indices actuels du jeu au moment du clic admin- Avant : un joueur ayant buzze avant tout indice pouvait etre penalise si l'admin attribuait les points apres qu'un indice ait ete revele
- Apres : chaque joueur conserve son contexte d'indices au moment de son buzz, independamment de ce qui se passe ensuite
- Correction dans
handleBumperClick(clic joueur) etonTeamClick(clic equipe)
- [QCM/UI]: Nouveau badge "+X pts" en phase
REVEALEDsur les cartes equipes ayant repondu correctement- Symetrique au badge Memory existant (meme emplacement, meme animation scale 0→1)
- Variante bleue (vs vert pour Memory) pour distinguer les deux types
- Affiche les points calcules avec la penalite individuelle (basee sur
HINTS_AT_BUZZ) - N'apparait que pour les equipes dont au moins un buzzer a la bonne
ANSWER_COLOR - Disparait automatiquement a la transition PREPARE (nouvelle question)
- [VPlayer/Fullscreen]: Ajout d'un bouton plein ecran discret sur la page joueur (
/vplayer)- Support cross-browser avec vendor prefixes (
webkit,moz) - Icone toggle :
⛶pour entrer,⊠pour sortir du plein ecran - Sur iOS Safari : la Fullscreen API n'est pas supportee sur
documentElement(limitation plateforme) — le bouton est sans effet mais n'errore pas - Bouton fade-out apres 3s, reapparait au survol/tap
- Support cross-browser avec vendor prefixes (
- [VPlayer/WakeLock]: Prevention de la mise en veille du telephone via Screen Wake Lock API
- Guard
'wakeLock' in navigatorpour les navigateurs sans support (iOS Safari) - Reacquisition automatique du wake lock apres retour d'onglet (
visibilitychange) - Liberation propre au demontage du composant
- Guard
- [VPlayer/Blink]: Clignotement du buzz ralenti de 3s a 1.5s par cycle (animation
buzz-pulse)
- [CI/CD]: Metadonnees Windows PE dans le binaire
buzzcontrol.exeviagoversioninfo- Proprietes Windows visibles (clic droit > Proprietes > Details) : Nom du produit
BuzzControl, Version, Description, Copyright - Nouveau fichier
server-go/cmd/server/versioninfo.json— template metadonnees PE (maintenu en phase avec la version courante) - Nouvelle icone
server-go/assets/icon.icointegree au binaire Windows - Step CI
Generate Windows PE metadatadans le job Windows de.github/workflows/release.yml— genereversioninfo.jsondynamiquement depuis le tag, puis compile le.sysoviagoversioninfo - Step
build.ps1— genere le.sysoavantgo buildpour les builds locaux Windows
- Proprietes Windows visibles (clic droit > Proprietes > Details) : Nom du produit
- [TV Display]: Effets neon/halo restaures sur le PlayerDisplay
- Le CSS
.default-question-imageetait incorrectement place dansConfigPage.cssau lieu dePlayerDisplay.css, ce qui empechait son application sur la TV - Suppression de la transformation
y: 20dans l'animation Framer Motion — evite la creation d'un layer GPU composite qui masquait le pseudo-element::afterde l'effet neon (conflit z-index) - Remplacement de
opacity: 0.75parfilter: brightness(0.7):opacitycree un stacking context qui bloque l'effet neon,filterpreservele rendu visuel sans bloquer le pseudo-element
- Le CSS
- [Backend]: Image par defaut pour les questions sans media (
/api/config/default-image)GET /api/config/default-image: sert l'image personnalisee ou le SVG embarque en fallbackPOST /api/config/default-image: upload d'une image personnalisee (jpg, png, gif, webp, svg)DELETE /api/config/default-image: supprime l'image personnalisee, retour au SVG embarque- Asset embarque :
server-go/assets/default-question-image.svg(icone buzzer SVG) - Broadcast
CONFIG_UPDATEapres upload/suppression avec champdefault_question_image_is_custom
- [Frontend/TV]: Affichage TV (
PlayerDisplay) — image par defaut pour questions NORMAL et QCM sans media- Utilise
/api/config/default-imagecomme fallback — toujours valide (custom ou SVG embarque)
- Utilise
- [Frontend/Config]: Section "Image par defaut" dans ConfigPage
- Apercu de l'image courante avec cache-busting (
?t=timestamp) apres upload/suppression - Bouton upload et bouton de suppression (affiche uniquement si image personnalisee active)
- Synchronisation en temps reel via
CONFIG_UPDATEWebSocket
- Apercu de l'image courante avec cache-busting (
- [Backend]: Initialisation de
defaultImageIsCustoma la connexion WebSocket- L'etat de l'image par defaut est correctement envoye aux nouveaux clients a leur connexion
- [QCM Engine]: Les hints QCM ne sont plus réinitialisés correctement lors d'un PREPARE sur la même question
QcmInvalidatedetait remis a zero uniquement quandisNewQuestion == true- Quand une meme question etait re-PREPAREe (ex: apres REVEAL), les hints revelés du cycle precedent restaient visibles
- Correction : le reset est deplace en dehors du guard
isNewQuestionpour garantir un etat propre a chaque PREPARE - Fichier :
server-go/internal/game/engine.go - Tests :
server-go/internal/game/engine_test.go(158 lignes de tests ajoutees)
- [Backend]:
BroadcasterManager— envoi de heartbeats UDP periodiques pour la decouverte automatique du serveur- Format :
BUZZ_SERVER|IP1|IP2|...|PORT\0(multi-interfaces, null-termine) - Intervalle normal : 5 secondes ; mode enrollment : 1 seconde
- Heartbeat immediat au demarrage pour connexion rapide des buzzers
- Detection automatique de toutes les IPs IPv4 actives du serveur (hors loopback et link-local)
- Broadcast sur toutes les adresses de broadcast des interfaces reseau actives
- Format :
- [Firmware]:
click_broadcaster.h— listener UDP AsyncUDP pour reception des heartbeats serveur- Parsing du format
BUZZ_SERVER|IP1|IP2|...|PORT(jusqu'a 8 IPs) - Stockage RAM uniquement (stateless, mis a jour a chaque heartbeat)
- Fonctions :
startBroadcastListener,parseBroadcastHeartbeat,hasBroadcastDiscovery,getBroadcastDiscovery
- Parsing du format
- [Firmware]: Failover multi-IPs dans
click_WifiManager.h- Chaine de fallback : broadcast → IP NVS → mDNS → retry broadcast
- Timeout broadcast : 30 secondes avant passage au fallback
- Essai de chaque IP decouverte dans l'ordre jusqu'a connexion reussie
- [Firmware]: Nouvelles phases LED dans la sequence de boot (phases 4 et 5)
- Phase 4 — Jaune pulsant 2 Hz : attente du heartbeat UDP (en cours de recherche serveur)
- Phase 5 — Bleu clignotant rapide : tentative de connexion sur chaque IP decouverte
- [Frontend]: Suppression des champs IP serveur et Port de la section WiFi de ConfigPage
- La configuration IP serveur n'est plus necessaire — decouverte automatique via UDP broadcast
- Simplification de l'interface de configuration buzzer
- Backend :
server-go/internal/server/broadcaster.go:BroadcasterManager(nouveau)server-go/internal/server/udp.go:UDPBroadcaster,BroadcastRaw, detection broadcast multi-interfacesserver-go/cmd/server/main.go: IntegrationBroadcasterManagerdans le cycle de vie du serveur
- Firmware :
src/BuzzClick/click_broadcaster.h: UDP listener, parser, etat de decouverte (nouveau)src/BuzzClick/click_WifiManager.h: Boot sequence enrichie (phases 4-5), chaine de fallback
- [USB UI]:
USBConfigModalunifiee — config WiFi AT et flash firmware reunis dans une seule modale- Point d'entree unique pour toutes les operations USB buzzer (remplace les deux interfaces separees)
- Selection de port USB unifiee : une seule connexion serie pour config WiFi ET flash firmware
- Bouton "Flash via USB" repositionne dans la section Firmware de ConfigPage
- [Firmware UI]: Badge firmware type dans ConfigPage (Full merged / App only)
- Indique si le firmware de reference est un build complet ou uniquement l'application
- [USB UI]: Bouton "Flash via USB" desactive automatiquement quand firmware app-only (IS_MERGED=false)
- Evite les flashs incorrects avec un firmware partiel
- [OTA Backend]: Champ
IS_MERGEDpropage via WebSocket dans le handlerFIRMWARE_VERSION- Le frontend recoit correctement le type de firmware (complet vs app-only) en temps reel
- [USB Flash]: Correction flash USB via esptool-js
- Ecriture depuis l'adresse 0x0 (au lieu de 0x10000) pour un flash complet
- Ajout hard_reset apres le flash pour redemarrage automatique du buzzer
- Verification AT+VERSION post-flash pour confirmer le succes de la mise a jour
- [USB UI]: La section "Flash via USB" de ConfigPage migree dans
USBConfigModal- Coherence UI — une seule modale pour toutes les operations USB
- Frontend:
web/src/components/USBConfigModal.jsx: Modale unifiee config WiFi AT + flash firmware (nouveau composant)web/src/pages/ConfigPage.jsx: Badge IS_MERGED + integration USBConfigModalweb/src/hooks/useEspFlash.js: Correction write 0x0 + hard_reset + verification AT+VERSION
- [OTA UI]: Bouton "Restaurer firmware embarqué" dans ConfigPage
- Affiche le bouton uniquement quand un firmware embarqué est disponible dans le binaire serveur
- Masque automatiquement le bouton si aucun binaire embarqué n'est présent
- Endpoint
POST /api/firmware/buzzclick/restore-embedded: restaure le firmware embarqué vers le stockage actif
- [OTA Backend]: Endpoint POST /api/firmware/buzzclick/restore-embedded
- Extrait le firmware embarqué depuis les assets Go (
server-go/assets/firmware/buzzclick-latest.bin) - Copie le binaire embarqué vers
data/firmware/buzzclick-latest.bin(stockage actif) - Broadcast
FIRMWARE_VERSIONpour rafraîchissement automatique de l'UI - Retourne 404 si aucun firmware embarqué n'est disponible dans le binaire
- Extrait le firmware embarqué depuis les assets Go (
- [OTA Backend]: Champ
EMBEDDED_VERSIONdansFirmwareVersionPayload- Expose la version du firmware embarqué dans les assets Go
- Permet au frontend de savoir si une restauration est possible et quelle version sera restaurée
- [OTA Firmware]: Progression OTA propagée via engine
OTA_PERCENTpropagé à travers le moteur de jeu pour un suivi centralisé- Architecture améliorée pour la gestion des événements OTA
- [OTA UI]: Barres de progression restent orange jusqu'au reboot du buzzer
IS_OUTDATEDn'est plus remis à zéro lors de la réception deOTA_PROGRESS done- Le badge firmware repasse au vert uniquement quand le buzzer reboot et envoie HELLO avec la nouvelle version
- [OTA UI]: Badge firmware cliquable dans TeamsPage pour déclencher OTA individuel
- [OTA Firmware]: BuzzClick construit l'URL OTA depuis l'IP serveur stockée en NVS
- Corrige la dépendance à une IP hardcodée pour le téléchargement OTA
- Utilise
server_ipdu NVS pour construire l'URLhttp://<server_ip>/api/firmware/buzzclick/latest.bin
- [OTA UI]: "Tout mettre à jour" dans ConfigPage ouvre OtaAllModal au lieu de déclencher directement l'OTA
- Backend :
internal/server/http_firmware.go: HandlerhandleRestoreEmbeddedFirmware()(nouveau)internal/server/http.go: RoutePOST /api/firmware/buzzclick/restore-embeddedinternal/protocol/messages.go: ChampEMBEDDED_VERSIONdansFirmwareVersionPayloadinternal/server/firmware.go: Lecture version depuis firmware embarqué
- Frontend :
web/src/pages/ConfigPage.jsx: Bouton restauration firmware embarqué + OtaAllModalweb/src/pages/TeamsPage.jsx: Badge firmware cliquable pour OTA individuel
- Assets :
server-go/assets/firmware/buzzclick-latest.bin: Firmware BuzzClick embarqué dans le binaire serveur
- [OTA Buzzer]: Mise a jour firmware OTA des buzzers BuzzClick directement depuis l'interface admin
- Backend :
FirmwareManagerstocke le firmware de reference dansdata/firmware/ - Stockage versionne :
buzzclick-vX.Y.Z.bin+buzzclick-latest.bin+version.txt - Endpoint
GET /api/firmware/buzzclick/version: info firmware de reference (version, filename, size) - Endpoint
GET /api/firmware/buzzclick/latest.bin: telechargement binaire pour les buzzers - Endpoint
POST /api/firmware/buzzclick/upload: upload firmware .bin via multipart form - Endpoint
POST /api/buzzer/{mac}/update: declenchement OTA individuel par adresse MAC - Endpoint
POST /api/buzzer/update-all: OTA en masse sur tous les buzzers obsoletes - Validation taille firmware : 200 KB minimum, 2 MB maximum
- Validation extension : seuls les fichiers
.binsont acceptes - Sanitisation version : prevention injection de chemin (path traversal)
- Broadcast
FIRMWARE_VERSIONapres upload pour rafraichissement UI automatique
- Backend :
- [OTA Buzzer]: Detection et affichage des buzzers avec firmware obsolete
- Champ
FIRMWARE_VERSIONajoute au modeleBumper(version reportee via HELLO) - Champ
IS_OUTDATEDcalcule automatiquement par comparaison semver - Champ
OTA_STATUSpour suivi de progression ("downloading", "flashing", "done", "error") - Reset de
OTA_STATUSautomatique lors de la reconnexion (HELLO) - Action WebSocket
FIRMWARE_VERSIONpour mise a jour UI en temps reel - Action WebSocket
OTA_UPDATE(server -> buzzer) : declenchement avec URL + version + taille - Action WebSocket
OTA_PROGRESS(buzzer -> server) : progression en pourcentage + statut
- Champ
- [OTA Firmware]: Support OTA dans le firmware BuzzClick (ESP32-C3)
- Module
click_otaManager.h: telechargement HTTP et flash sur partition OTA - Reception commande
OTA_UPDATEvia WebSocket avec URL du firmware - Progression reportee via
OTA_PROGRESS(downloading 0-100%, flashing, done, error) - Rollback automatique ESP32 si le nouveau firmware ne demarre pas
firmware_versioninclus dans le message HELLO pour identification automatique
- Module
- [OTA UI]: Interface admin pour gestion OTA dans ConfigPage
- Section "Gestion Firmware" : affichage version de reference, filename, taille, statut
- Upload firmware .bin avec validation et feedback toast
- Bouton "Mettre a jour tous" (buzzers obsoletes uniquement)
- Modal OTA par buzzer : version actuelle vs reference, bouton "Lancer la mise a jour OTA"
- Statut OTA inline sur chaque buzzer dans TeamCard
- [OTA UI]: Indicateurs visuels firmware dans TeamCard
- Badge
fw: X.Y.Zrouge si buzzer obsolete, gris si a jour - Clic sur badge rouge ouvre la modal OTA
- Statut OTA inline (downloading / flashing / done / error)
- Badge
- [USB Flash]: Flash firmware via USB depuis la modal de configuration USB
- Nouvel onglet/section "Flash Firmware" dans
USBConfigModal - Hook
useEspFlash: telechargement firmware depuis serveur + flash viaesptool-js - Barre de progression + logs de flash en temps reel
- Arret automatique de la connexion AT avant le flash (liberation du port serie)
- Nouvel onglet/section "Flash Firmware" dans
- [WiFi Config Broadcast]: Diffusion configuration WiFi aux buzzers connectes
- Nouveau champ
ssid2/password2dansWiFiDefaultsConfig(reseau WiFi de secours) - Endpoint
POST /api/buzzer/wifi-config: broadcast WIFI_CONFIG a tous les buzzers WS - Methode
ConnectedCount()ajoutee aBuzzerWebSocketHub - Action WebSocket
WIFI_CONFIG(server -> buzzer) avec SSID, PASS, SERVER_IP, PORT, SSID2, PASS2 - Auto-sync : envoi WIFI_CONFIG automatique a chaque nouveau buzzer qui se connecte
- Firmware BuzzClick : reception WIFI_CONFIG, sauvegarde NVS, reboot si config changee
- Support SSID2/PASS2 dans le NVS du firmware pour reseau WiFi de secours
- Nouveau champ
- [WiFi Config UI]: Champs SSID2/Password2 et bouton broadcast dans ConfigPage
- Deux nouveaux champs "Reseau WiFi 2" (SSID + mot de passe de secours)
- Bouton "Diffuser config WiFi" (envoie aux buzzers WebSocket connectes)
- Chargement depuis
config.jsonau montage de la page
- Backend :
internal/server/firmware.go:FirmwareManager(GetInfo, SaveFirmware, IsOutdated, compareSemver)internal/server/http_firmware.go: Handlers OTA (version, download, upload, update, update-all)internal/server/http.go: Routes/api/firmware/*,/api/buzzer/{mac}/update,/api/buzzer/update-all,/api/buzzer/wifi-configinternal/protocol/messages.go: Actions OTA_UPDATE, OTA_PROGRESS, FIRMWARE_VERSION, WIFI_CONFIG + payloadsinternal/game/models.go: Champs Bumper.FirmwareVersion, Bumper.IsOutdated, Bumper.OTAStatusinternal/config/config.go: Champs WiFiDefaultsConfig.SSID2, WiFiDefaultsConfig.Password2internal/server/websocket_buzzer.go: Methode ConnectedCount()cmd/server/main.go: sendWifiConfigToBuzzer(), broadcastWifiConfig(), OTA_PROGRESS handler
- Frontend :
web/src/hooks/useEspFlash.js: Hook flash firmware via esptool-js (nouveau fichier)web/src/components/USBConfigModal.jsx: Section flash firmware USBweb/src/components/TeamCard.jsx: Badge firmware version + modal OTA + statut inlineweb/src/pages/ConfigPage.jsx: Section firmware OTA + champs WiFi2 + bouton broadcastweb/src/hooks/useWebSocket.js: Handler FIRMWARE_VERSION
- Firmware :
src/BuzzClick/click_otaManager.h: Module OTA HTTP download + flash (nouveau fichier)src/BuzzClick/click_serverConnection.h: Handler OTA_UPDATE, handler WIFI_CONFIGsrc/BuzzClick/click_websocketClient.h: Inclusion firmware_version dans HELLOsrc/BuzzClick/click_nvsConfig.h: Champs wifi_ssid2, wifi_pass2 dans BuzzClickConfig
- Tests :
internal/server/firmware_test.go: Tests FirmwareManager (SaveFirmware, GetInfo, IsOutdated, compareSemver)internal/server/firmware_http_test.go: Tests HTTP integration endpoints firmware
- [OTA UI]: Barres de progression restent orange jusqu'au reboot du buzzer (pas de passage prématuré au vert)
- [OTA UI]: Barres de progression conservées par buzzer après reboot dans OtaAllModal
- [OTA UI]: Fermeture manuelle des modals (suppression de l'auto-close)
- [OTA Firmware]: URL backward-compatible dans OTA_UPDATE pour firmware < 3.1.2
- [OTA Firmware]: IS_OUTDATED restreint aux buzzers physiques WebSocket uniquement (pas les clients web)
- [OTA Firmware]: Fallback IS_OUTDATED + version firmware de référence + comptage buzzers obsolètes
- [OTA UI]: Affichage version firmware + support firmware embarqué
- [OTA Backend]: Retourne version sanitisée dans la réponse upload, suppression route morte
- [OTA Backend]: Reset OTA_STATUS lors de la reconnexion buzzer (HELLO)
- [OTA Backend]: Clés JSON majuscules dans FirmwareVersionPayload pour cohérence frontend
- [Security]: Sanitisation de la chaîne de version dans SaveFirmware (prévention path traversal)
- [UI]: Synchronisation firmwareInfo depuis WebSocket dans ConfigPage (utilisation hook property)
- [Firmware]: Flash USB via binaire merged + snapshot progression OTA done
- [OTA UI]: Barres de progression orange jusqu'au reboot, vert sur reboot confirmé
- OTA requiert une connexion WiFi stable (~500KB-1MB par buzzer)
- Duree OTA estimee : 30-60 secondes par buzzer
- Rollback automatique ESP32 si le firmware ne demarre pas apres flash
- WIFI_CONFIG necessite que le buzzer soit connecte via WebSocket (pas TCP)
- Flash USB via esptool-js requiert Chrome/Edge 89+ sur localhost
- Flash USB supporte le binaire merged (bootloader + partition table + app)
- [BuzzClick Firmware]: Cycle gris 1/3 supprime pendant boot grace au flag
bootComplete- Le gray rotation s'executait des le boot et ecrasait tous les patterns LED
- Flag
bootComplete = falseinitialise, mis atrueapres Phase 6 (HELLO ack) - Fichiers :
click_serverConnection.h,click_MAIN.cpp
- [BuzzClick Firmware]: Phase 3 orange 2/4 maintenant visible
resetGame()etconnectWebSocket()ecrasaient le pattern Phase 3- Deplace
resetGame()avant Phase 3, supprime yellow LED overwrite dans WebSocket - Fichiers :
click_WifiManager.h,click_websocket_espidf.h,click_websocketClient.h
- [BuzzClick Firmware]: Delays 500ms ajoutes pour visibilite de toutes les phases boot
- Toutes les phases boot ont maintenant un
delay(500)pour etre visibles
- Toutes les phases boot ont maintenant un
- [BuzzClick Firmware]: Nouveau pattern LED boot (6 phases)
- Phase 1 : RED 1/2 (12 LEDs) - Boot start (RED 1/4 → 1/2)
- Phase 2 : RED 1/4 (6 LEDs) - WiFi connecting (ORANGE → RED)
- Phase 3 : ORANGE 1/4 (6 LEDs) - WiFi connected (ORANGE 2/4 → 1/4)
- Phase 4 : ORANGE 2/4 (12 LEDs) - WebSocket connecting (NOUVEAU)
- Phase 5 : GREEN 2/4 (12 LEDs) - WebSocket connected (inchange)
- Phase 6 : GREEN 3/4 (17 LEDs) - HELLO ack (inchange)
- [BuzzClick Firmware]: Fixed LED superposition when assigning team color
- Added
stopGrayRotation()function to clear all gray LEDs and stop animation before displaying team color - Prevents visual artifact where gray animation LEDs persisted when team color was applied
- Ensures clean transition from gray animation to team color display
- Added
- [BuzzClick Firmware]: Fixed gray LED animation not restarting when buzzer removed from team
- Enhanced team detection in
handleUpdateAction()andhandleReadyAction()to check for empty TEAM field - Three cases handled: TEAM present+non-empty (show team color), TEAM present+empty (start gray animation), TEAM absent (start gray animation)
- Previously, when removing a buzzer from a team, the LED stayed on the last team color instead of restarting the gray rotation
- Enhanced team detection in
- [BuzzClick Firmware]: Fixed WebSocket message fragmentation detection
- Replaced null-terminator detection (
\0) with JSON brace counting algorithm - Correctly detects complete JSON messages by counting
{and}while respecting strings and escape sequences - Parses message when brace count returns to 0, indicating complete JSON
- WebSocket protocol uses frame metadata (FIN bit), not null terminators like TCP
- Added 64KB buffer limit with overflow protection
- Replaced null-terminator detection (
- [BuzzClick Firmware]: Added ACTION READY support via WebSocket
- New
handleReadyAction()function parses READY messages and extracts team color from BUMPER field - Handles both UPDATE and READY actions for team assignment
- New
- [BuzzClick Firmware]: Added gray LED animation when no team assigned
- Displays 1 LED out of 3 in gray (RGB 64,64,64) rotating every 200ms
- Animation starts when buzzer not assigned to any team
- Functions:
startGrayRotation(),updateGrayRotation()called from main loop
- [BuzzClick Firmware]: Initial WebSocket fragmentation handling attempt (later fixed in v3.0.5)
- Attempted null-terminator detection for message completion (incorrect approach)
- Added fragment buffer to accumulate partial WebSocket frames
- [BuzzClick Firmware]: Factory reset now persists after reboot
- Added
ESP.restart()afternvsClearConfig()incheckBootButton()to force immediate reboot after clearing NVS - Ensures empty WiFi config is reloaded on boot, preventing unwanted WiFi autostart
- Previously, the buzzer would reconnect to the old WiFi network after factory reset because the cleared NVS values were not re-read until the next power cycle
- Added
- [BuzzClick Firmware]: Fixed build error when USE_WEBSOCKET=1
- Added conditional compilation guards in
WiFiGotIP()to useconnectWebSocket()for WebSocket mode,connectSRV()+initBroadcastUDP()for TCP mode - Resolves declaration error for
initBroadcastUDP()andconnectSRV()when WebSocket protocol is enabled
- Added conditional compilation guards in
- [WebSocket Buzzer]: Support protocole WebSocket pour buzzers physiques (mode hybride TCP+WS)
- Nouveau endpoint
/ws/buzzerdedie aux connexions des buzzers physiques BuzzClick - Hub
BuzzerWebSocketHubsepare du hub web (WebSocketHub) pour isolation des clients - Identification des buzzers via adresse MAC (parametre query ou message HELLO)
- Messages JSON standards (HELLO, BUTTON, PONG) sans terminateur null (
\0) - Broadcast vers tous les buzzers WebSocket (LED_ON, LED_OFF, START, STOP, PING)
- Envoi cible a un buzzer specifique via adresse MAC
- Ping/pong WebSocket natif (keep-alive 30s) pour detection de deconnexion
- Read deadline 60s avec renouvellement automatique sur activite
- Canal de messages entrants buffered (capacite 100) avec drop gracieux si plein
- Gestion concurrente thread-safe (mutex RWLock sur la map clients)
- Nouveau type de client
buzzerdans l'enumClientType - Champ
MACajoute a la structWebSocketClientpour identification des buzzers - Champ
PROTOCOLajoute au modeleBumperpour identifier le protocole de connexion ("TCP" ou "WebSocket") - Compteurs de buzzers dans le broadcast
CLIENTS:BUZZER_TCP_COUNTetBUZZER_WS_COUNT - Retrocompatibilite complete : le serveur TCP port 1234 reste actif en parallele
- Les buzzers anciens (TCP) et nouveaux (WebSocket) coexistent sans conflit
- Nouveau endpoint
- [Firmware WebSocket]: Client WebSocket pour BuzzClick (ESP32-C3)
- Classe
WebSocketBuzzerClientdansclick_websocketClient.h(active avec flagUSE_WEBSOCKET) - Connexion a
ws://<server_ip>/ws/buzzervia bibliotheque ArduinoWebsockets - Reconnexion automatique avec backoff exponentiel (1s, 2s, 4s, 8s max)
- Messages JSON : HELLO (enregistrement), BUTTON (buzz), PONG (ready-check)
- LED indicateur : Vert = connecte, Jaune clignotant = reconnexion, Rouge = deconnecte
- Classe
- Backend :
internal/server/websocket_buzzer.go: Hub WebSocket dedie aux buzzers (BuzzerWebSocketHub)HandleConnection(): Upgrade HTTP, extraction MAC query param, creation clientreadPump(): Lecture messages, identification MAC, dispatch vers canal IncomingwritePump(): Ecriture messages, ping keep-alive 30s, drain queueBroadcast()/BroadcastRaw(): Diffusion a tous les buzzers connectesSendToClient(): Envoi cible par MAC addressSetClientMAC()/GetClients()/BuzzerCount(): Gestion des clients- Callback
OnBuzzerChangepour notification des changements de connexion
internal/server/websocket.go: AjoutClientTypeBuzzeret champMACsurWebSocketClientinternal/server/http.go: Nouveau handler/ws/buzzer, injectionBuzzerWebSocketHubdans HTTPServerinternal/game/models.go: ChampProtocolsur structBumper("TCP" ou "WebSocket")internal/protocol/messages.go:SerializeForWebSocket(),ClientsPayloadavecBUZZER_TCP_COUNT/BUZZER_WS_COUNTinternal/protocol/parser.go: FonctionParseSingle()pour messages WebSocket individuelscmd/server/main.go:handleHello()injecte le protocole,broadcastClientCounts()unifie les compteurs
- Firmware :
src/BuzzClick/click_websocketClient.h: Client WebSocket avec ArduinoWebsockets- Classe
WebSocketBuzzerClientavec connect/loop/send/reconnect - Backoff exponentiel (1s-8s)
- LED indicateurs selon etat de connexion
- Fonctions wrapper :
ws_sendBuzz(),ws_sendPong(),ws_isConnected(),ws_connect()
- Classe
- Tests :
internal/server/websocket_buzzer_test.go: 13 tests unitaires ciblantBuzzerWebSocketHub:- Connexion/deconnexion buzzers (simple, multiple)
- Reception messages JSON (HELLO avec MAC, BUTTON avec payload, PONG)
- Broadcast et envoi cible (SendToClient par MAC)
- Identification MAC via message HELLO
- Acces concurrent (5 buzzers simultanes)
- Gestion canal Incoming plein
- Retrocompatibilite : Les buzzers avec ancien firmware (TCP) continuent de fonctionner avec le serveur v3.0.0
- Mode hybride : Le serveur supporte TCP (port 1234) ET WebSocket (port 80,
/ws/buzzer) simultanement - Firmware : Client WebSocket disponible via flag de compilation
USE_WEBSOCKET(TCP par defaut) - Performance : Latence WebSocket attendue entre 15-40ms (vs 10-30ms TCP), acceptable pour le jeu
- [CI/CD]: Compilation automatique du firmware BuzzClick dans GitHub Actions
- Nouveau job
compiling-firmwares'exécutant en parallèle des builds Windows/Linux - Versioning unifié : serveur et firmware partagent désormais le même numéro de version
- Injection automatique de la version dans
platformio.inivia sed - Validation de taille du binaire firmware (200KB-2MB)
- Chaque release GitHub contient désormais 3 binaires :
buzzcontrol-vX.Y.0-windows-amd64.exe(serveur Windows, ~8-9 MB)buzzcontrol-vX.Y.0-linux-arm64(serveur Raspberry Pi, ~8 MB)buzzclick-vX.Y.0-firmware.bin(firmware ESP32-C3, ~500KB-1MB)
- Nouveau job
- [Documentation]: Guide complet de mise à jour firmware
docs/FIRMWARE_UPDATE.md: Guide utilisateur pour flasher les buzzers BuzzClick- 3 méthodes documentées : GitHub Release (recommandé), build depuis sources, OTA (à venir)
- Troubleshooting détaillé pour problèmes courants de flash
- Tableau de compatibilité firmware/serveur
- [Documentation]: Commandes PlatformIO étendues dans
docs/DEV_COMMANDS.md- Section "Firmware BuzzClick (ESP32-C3)" remplace "ESP32 (legacy)"
- Commandes d'installation PlatformIO CLI
- Build, flash manuel via esptool, monitoring série
- Instructions de versioning firmware
- [Documentation]: Workflow CI/CD documenté dans
CLAUDE.md- Nouvelle section "CI/CD et Release Automatique"
- Description des 3 jobs de compilation (Windows, Linux ARM64, Firmware)
- Explication du versioning unifié depuis v2.54.0
- Durée totale du pipeline : ~3-4 minutes
- [Documentation]: Diagramme CI mis à jour dans
docs/RELEASE_PROCEDURE.md- 3 jobs de compilation en parallèle au lieu de 2
- Vérification de 4 jobs (checking + 3 compiling) au lieu de 3
- Durée estimée ajustée : ~3-4 minutes au lieu de ~2-3 minutes
- CI/CD:
.github/workflows/release.yml: Jobcompiling-firmwareajouté- Installation de Python 3.11 et PlatformIO CLI sur runner Ubuntu
- Cache pip pour accélérer les builds ultérieurs
- Validation binaire firmware (taille min 200KB, max 2MB)
- Artefact
firmware-buzzclickuploadé pour le jobreleasing - Job
releasingdépend maintenant de[checking, compiling, compiling-firmware]
- Rétrocompatibilité : Les buzzers avec firmware 1.209.3 (anciennes versions) continuent de fonctionner avec les nouveaux serveurs
- Protocole TCP/UDP : Aucune modification du protocole de communication dans cette version
- Durée CI : L'ajout du job firmware n'impacte pas significativement la durée totale grâce à l'exécution en parallèle
- [VJoueur]: Interface QCM tactile multicolore pour joueurs virtuels
- Les VJoueurs peuvent répondre aux questions QCM en touchant directement une des 4 couleurs (Rouge, Vert, Jaune, Bleu)
- Badge multicolore (4 quartiers colorés) affiché dans TeamCard pour identifier les VJoueurs
- Invalidation automatique des buzzers physiques d'une équipe quand elle a un VJoueur actif en mode QCM
- Indicateurs visuels (grisés) des buzzers physiques invalidés dans l'interface admin
- Nouvelle action WebSocket
VPLAYER_QCM_ANSWERavec payload{ANSWER_COLOR: "RED"|"GREEN"|"YELLOW"|"BLUE"} - Champ
IS_VPLAYERajouté au modèle Bumper pour différencier les joueurs virtuels des buzzers physiques
- Backend:
models.go: Nouveau champIS_VPLAYER booldans la structure Bumpermessages.go: Action WebSocketVPLAYER_QCM_ANSWERpour réponses tactiles QCMengine.go: Logique d'invalidation des buzzers physiques si l'équipe a un VJoueur actif en QCM- Tests unitaires:
TestVPlayerBumperCreation,TestVPlayerQCMBuzzAllColors,TestPhysicalBuzzerInvalidatedForQCM,TestPhysicalBuzzerNotInvalidatedForNonQCM
- Frontend:
TeamCard.jsx: Badge SVG 4 quartiers pour VJoueursVPlayerPage.jsx: Interface tactile 4 boutons colorés pendant les questions QCM STARTEDGamePage.jsx: Indicateurs visuels (grisés) des buzzers physiques invalidés par VJoueur
- [QCM]: Marqueurs d'indices sur la barre de temps
- Traits verticaux orange/jaune positionnés sur la barre de progression du timer
- Indiquent visuellement quand les indices QCM (invalidation de mauvaises réponses) vont se déclencher
- Animation de pulsation quand le timer approche d'un seuil (15% avant)
- Animation de fade-out quand l'indice est déclenché
- Respect des contraintes de sécurité backend (seuil1 >= 2s, seuil2 >= 1s, écart >= 1s)
- Visible uniquement si
QCM_HINTS_ENABLED = truesur la question
- Frontend:
Timer.jsx: Nouveau prophintMarkerspour afficher des marqueurs sur la barre de tempsTimer.css: Styles.hint-marker, animationshint-marker-pulseethint-marker-fadePlayerDisplay.jsx: Calcul des positions des marqueurs viauseMemoà partir deQCM_HINT_THRESHOLD_1/2
- [Memory]: Modes de jeu multi-équipes (Phase 6)
- Mode SOLO: Une seule équipe joue (comportement par défaut, rétrocompatible)
- Mode CHACUN_SON_TOUR: Rotation stricte après chaque tentative (2 cartes), que la paire soit trouvée ou non
- Mode TANT_QUE_JE_GAGNE: L'équipe garde la main tant qu'elle trouve des paires valides, rotation uniquement sur erreur
- Sélection interactive des équipes participantes en phase PREPARE
- Synchronisation multi-admin de la sélection d'équipes via WebSocket
- Validation flexible: minimum 2 équipes requises au START (pas pendant la sélection)
- Indicateur visuel de l'équipe courante sur l'affichage TV (badge coloré)
- Mise en évidence de l'équipe courante uniquement en phase STARTED/PAUSED
- Tableau des scores par équipe en temps réel
- Tri automatique des équipes par performance (paires trouvées, puis erreurs)
- Attribution des points par équipe avec affichage individuel
- Bonus de complétion attribué à l'équipe qui trouve la dernière paire
- Reset complet de l'état Memory lors de la sélection d'une nouvelle question
- Badge "+X pts" remplaçant visuellement la pastille "PRET" en phase REVEALED
- Backend:
models.go: TypeMemoryModeavec constantes (SOLO, CHACUN_SON_TOUR, TANT_QUE_JE_GAGNE)models.go: Nouveaux champs GameState (MemoryCurrentTeam,MemoryTeamPairs,MemoryParticipatingTeams)messages.go: Action WebSocketMEMORY_SET_TEAMSavec payload Teamsengine.go: FonctionSetMemoryParticipatingTeams()avec validationengine.go: FonctionrotateToNextTeam()pour rotation circulaireengine.go: Logique de rotation conditionnelle dansFlipMemoryCard()selon le modeengine.go: Reset de l'état Memory dansReady()lors du changement de questionengine.go: Rotation d'équipe déplacée dansClearMemoryFlippedCards()après masquage des cartesmain.go: HandlerhandleMemorySetTeams()pour réception de la sélection d'équipes
- Frontend:
PlayerDisplay.jsx: Badge équipe courante avec couleur et animationPlayerDisplay.jsx: Tableau scores temps réel avec tri dynamiquePlayerDisplay.jsx: Mise en évidence conditionnelle selon phase du jeuGamePage.jsx: Interface sélection équipes (checkboxes, drag & drop)GamePage.jsx: Synchronisation WebSocket de la sélection (serveur = source de vérité)GamePage.jsx: Tri des équipes pour affichage (hookdisplayTeams)TeamCard.jsx: Badge points Memory à la position du badge PRETQuestionsPage.jsx: Sélecteur mode Memory (radio buttons SOLO/CHACUN_SON_TOUR/TANT_QUE_JE_GAGNE)
Filtrage des releases incomplètes :
- Exclusion des releases en draft (non publiées)
- Exclusion des prereleases (beta/alpha)
- Exclusion des releases sans binaires (CI non terminée)
- Exclusion des releases avec binaires < 1MB (upload en cours)
Mise à jour automatique du serveur :
- Vérification des nouvelles versions via GitHub Releases API
- Badge de notification dans la navbar si mise à jour disponible
- Page dédiée
/admin/updatespour gérer les mises à jour- Liste compacte des versions (1 par ligne)
- Icônes de statut : ✅ actuelle, ⬆️ plus récente,
⚠️ obsolète - Titres descriptifs extraits automatiquement du changelog
- Notes de version dépliables avec rendu Markdown
- Badge "Version locale" pour versions non publiées
- Téléchargement avec vérification de taille (40 MB min)
- Application avec backup automatique et rollback en cas d'échec
- Redémarrage automatique avec polling côté client
- Nouveaux endpoints REST : GET/POST /api/updates/*
- Cache GitHub API (1h) pour éviter rate limiting
- Option config
auto_check_updates(défaut: true) - Parseur Markdown léger pour les notes de version
Dedicated Backup/Restore Page :
- Nouvelle page dédiée pour la gestion des sauvegardes, restaurations et réinitialisations
- Accessible via le menu abeille (dropdown) du header
- Page complète :
/admin/backupet/anim/backup - Interface déplacée depuis ConfigPage vers une page dédiée
- Design: 3 sections avec cartes distinctes (Sauvegarde, Restauration, Réinitialisation)
- Responsive: 3-colonnes sur desktop, single-colonne sur mobile
Server Parameters Configuration :
- Exposition des paramètres serveur dans la page Configuration
auto_open_browsers: Ouvrir les navigateurs automatiquement au démarragedebug: Activer le mode debug pour les logs serveur- Nouvelle section "Parametres serveur" dans ConfigPage
- Chargement depuis
/config.jsonau montage - Sauvegarde via POST
/config.jsonavec feedback utilisateur - Intégration harmonieuse avec les sections existantes
- Responsive design mobile
Background Management Relocation :
- Déplacement de la gestion des fonds d'écran de ConfigPage vers QuestionsPage
- Section "Fonds d'écran" retirée de la page Configuration
- Intégrée dans la barre latérale de la page Questions avec design collapsible
- Nouvelle fonctionnalité : bouton toggle (▶/▼) pour économiser l'espace
- Grille adaptée à la largeur de la sidebar (2 colonnes au lieu de 4)
- Upload, durée, opacité, drag-drop toujours fonctionnels
Player Card Styling :
- Remplacé la couleur d'équipe (couleur QCM) par un gris neutre pour les cartes joueurs
- Avant : Fond coloré selon la couleur QCM du joueur (rouge, vert, jaune, bleu)
- Après : Fond gris neutre (#f3f4f6) cohérent pour tous les joueurs
- Les bordures latérales colorées (team-color) restent visibles
- Les indicateurs de couleur QCM restent visibles dans les badges
Configuration Page Cleanup :
- Suppression des sections Sauvegarde, Restauration, Réinitialisation de ConfigPage
- ConfigPage conserve : Neon, Server Params, Demo, Reset Scores
- Gain d'espace et clarté de la page de configuration
UI Styling :
- Suppression collapse/expand button de la section Fonds d'écran (QuestionsPage)
- Toggle logique conservé mais plus de bouton visuel
- Amélioration UX : actions simplifiées
CLAUDE.md mis à jour :
- Section Key Files détaillée avec toutes les pages frontend
- Documentation de l'organisation UI (menu principal, menu abeille)
- Répartition des fonctionnalités par page
- Décisions d'architecture v2.49.0 documentées
Navigation Navbar :
- Menu déroulant sur le logo abeille BuzzControl
- Clic sur l'abeille 🐝 ouvre/ferme le menu
- Menu contient 2 options : ⚙️ Config et 📋 Logs
- Fermeture au clic extérieur ou sur un item
- Animation slideDown fluide
- Accessibilité : aria-label et title présents
Nouveau groupe "Pages" dans la navbar :
- Zone dédiée aux pages TV et joueurs
- Label vertical "Pages" avec icône
- Liens : 📺 TV et 👥 Joueurs
- Même design que zones "Jeu" et "Config"
- Cohérence visuelle améliorée
-
Navbar restructuring : Config et Logs retirés de la navbar principale
- Avant : 8 liens visibles [Jeu|Scores|Palmarès|Historique|Joueurs|Questions|Config|Logs]
- Après : 8 liens + menu déroulant [🐝▼|Jeu|Scores|Palmarès|Historique|Joueurs|Questions|📺 TV|👥 Joueurs]
- Navbar restructurée avec 3 zones : Jeu | Config | Pages
- TV et Joueurs accessibles directement depuis la navbar
- Pastille de connexion intacte
-
GamePage UI improvement : Label "Affichage TV" changé en "TV" vertical
- Alignment avec le style de la navbar
- Label vertical centré et cohérent
- Better space efficiency
Fichiers modifiés :
server-go/web/src/components/Navbar.jsx: Ajout useState, useRef, useEffect pour gestion menu + groupe Pagesserver-go/web/src/components/Navbar.css: Styles menu, animations, responsive + Pages groupserver-go/web/src/pages/GamePage.jsx: Label "TV" vertical au lieu de "Affichage TV:"
Implémentation :
- État React
isMenuOpenavec useState - Fermeture au clic extérieur via useEffect + useRef + document.addEventListener
- NavLink conservé pour navigation SPA
- Animation CSS keyframe
slideDown(200ms) - CSS variables pour cohérence (colors, spacing, z-index)
Tests :
- 8 scénarios E2E validés ✅
- QA Report : VALIDATED (100% pass rate)
- Responsive design vérifiée (600px - 1920px)
- Accessibilité WCAG 2.1 Level A
- ✅ Non-breaking change
- ✅ Backward compatible
- ✅ Pas de changement API
- ✅ Pas de changement WebSocket
- ✅ Pas de migration requise
- Effet Néon: Paramètres de pulsation du glow correctement transmis via WebSocket
- Ajout de 3 champs manquants dans
NeonEffectPayload(glow_pulse_speed, glow_pulse_min, glow_pulse_max) - Correction de la sérialisation dans
broadcastConfigUpdate()etsendStateToClient() - Vitesse de pulsation maintenant configurable (0.5-5s)
- Amplitude min/max du glow appliquée correctement
- Ajout de 3 champs manquants dans
- UI Configuration: Amélioration de l'organisation des paramètres néon
- Bouton mode "Barre" renommé en "Neon" (plus clair)
- Slider "Intensité" déplacé vers section "Arc lumineux" (meilleure cohérence)
Correction de sécurité critique :
- Les VJoueurs (joueurs virtuels) se connectent maintenant correctement avec un type de client distinct
- Avant : VJoueurs = admin par défaut (risque sécurité)
- Après : VJoueurs = type "vplayer", séparé d'admin et TV
Détails :
- Ajout du type de client
vplayerdans l'enum ClientType (serveur) - VPlayerPage envoie
SET_CLIENT_TYPE { TYPE: "vplayer" }au montage - EnrollPage envoie
SET_CLIENT_TYPE { TYPE: "vplayer" }avant l'inscription - Serveur broadcast CLIENTS avec 3 compteurs : admin, tv, vplayer
- Navbar affiche les 3 compteurs distinctement
Fichiers modifiés :
server-go/internal/server/websocket.go: Ajout ClientTypeVPlayerserver-go/cmd/server/main.go: handleSetClientType supporte "vplayer"server-go/web/src/hooks/useWebSocket.js: clientCounts inclut vplayerserver-go/web/src/pages/EnrollPage.jsx: Appelle setClientType('vplayer')server-go/web/src/pages/VPlayerPage.jsx: Appelle setClientType('vplayer')server-go/web/src/components/Navbar.jsx: Affiche les 3 compteurscontracts/websocket-actions.md: Documentation SET_CLIENT_TYPE + CLIENTS
Modes d'affichage :
-
Mode "bar" (défaut) : Tube lumineux fin avec centre blanc et rotation d'arc
- Tube fixe avec 3 couches (externe floutée, centrale précise, centre blanc)
- Arc rotatif au centre du tube avec hotspot blanc brillant
- Proportions équilibrées : 1/3 par couche (blur, tube, glow central)
-
Mode "halo" : Effet néon classique avec bordure lumineuse large
- Conic-gradient rotatif avec arc lumineux configurable
- Glow pulsant autour de l'écran
Paramètres configurables (Page Configuration) :
| Paramètre | Plage | Défaut | Description |
|---|---|---|---|
enabled |
bool | false | Activer/désactiver l'effet |
mode |
"bar" / "halo" | "bar" | Type d'effet visuel |
arc_width |
30-180° | 60° | Largeur de l'arc lumineux |
intensity_gap |
0-100% | 80% | Écart d'intensité (opacité zone sombre) |
rotation_speed |
1-10s | 4s | Vitesse de rotation de l'arc |
bar_offset |
10-100px | 20px | Distance du tube par rapport au bord (mode bar) |
bar_thickness |
2-20px | 4px | Épaisseur du tube lumineux (mode bar) |
arc_blur |
0-200% | 100% | Flou de l'arc (% de bar_thickness) |
glow_pulse_speed |
0.5-5s | 2s | Vitesse de pulsation du glow |
glow_pulse_min |
0-100% | 30% | Opacité minimale du glow pulsant |
glow_pulse_max |
0-100% | 50% | Opacité maximale du glow pulsant |
Caractéristiques techniques :
- Couleur automatique selon la catégorie de la question
- Animations CSS GPU-accelerated (@property + conic-gradient)
- Diffusion temps réel via WebSocket (ACTION: CONFIG_UPDATE)
- Phases actives : READY, COUNTDOWN, STARTED, PAUSED
- Ajustement automatique des marges pour éviter chevauchement avec contenu
- [Positionnement] : Préservation du
position: fixedsur PlayerDisplay - [Marges] : Ajustement dynamique des marges de contenu selon
bar_offset - [Centrage] : Arc rotatif parfaitement centré sur le tube en mode bar
- [Proportions] : Équilibre visuel des 3 couches du tube (1/3 chacune)
- [Configuration] : Restauration des valeurs par défaut correctes dans config.json
Backend :
server-go/internal/config/config.go: NeonEffectConfig avec 11 paramètresserver-go/internal/protocol/messages.go: ACTION CONFIG_UPDATEserver-go/cmd/server/main.go: Broadcast CONFIG_UPDATE aux clients
Frontend :
server-go/web/src/styles/neon.css: Modes bar/halo, animations CSSserver-go/web/src/pages/ConfigPage.jsx: UI complète avec 2 onglets (Structure, Glow)server-go/web/src/pages/ConfigPage.css: Styles sliders et sections néonserver-go/web/src/pages/PlayerDisplay.jsx: Application classes + variables CSSserver-go/web/src/pages/PlayerDisplay.css: Marges dynamiques selon bar_offsetserver-go/web/src/pages/VPlayerPage.css: Support effet néon sur mobileserver-go/web/src/hooks/useWebSocket.js: Handler CONFIG_UPDATE
Documentation :
docs/ADMIN_GUIDE.md: Section complète effet néon avec guide visueldocs/DEV_PROCEDURE.md: Ajout étape rebuild frontend obligatoire.claude/commands/deploy.md: Procédure rebuild frontend avant build Go
-
[Tri Rapidité]: Persistance du tri jusqu'à PREPARE
- Avant : Les cartes reprenaient leur place dès STOP
- Après : Le tri par temps de buzz persiste en STARTED/PAUSED/REVEALED/STOPPED
- Reset : Uniquement lors de la sélection d'une nouvelle question (PREPARE)
-
[TeamCard]: Animation par-dessus les autres cartes
- zIndex dynamique : Cartes actives (zIndex: 10) passent au-dessus des autres (zIndex: 1)
- Effet : Animations de réorganisation plus fluides et visibles
-
[TeamCard]: Suppression des temps de réponse en double
- Supprimé : Temps vert sur la carte équipe (team-response-time)
- Supprimé : Temps gris sur chaque joueur (buzzer-response-time)
- Raison : Le temps existant sur la carte suffit
- [VPlayer]: Page VJoueur visible pendant ENROLL
- Problème : VPlayers voyaient le QR Code au lieu de leur interface
- Solution : Condition
gameState.phase === 'ENROLL' && !isVPlayer
server-go/web/src/pages/GamePage.jsx: Condition tri étendue à STOPPEDserver-go/web/src/components/TeamCard.jsx: zIndex + suppression temps + condition triserver-go/web/src/pages/PlayerDisplay.jsx: Condition ENROLL pour VPlayersserver-go/config.json: Version 2.45.0
- [TeamCard]: Correction de la visibilité des animations de réorganisation
- Problème : Animations framer-motion des joueurs/équipes invisibles lors du tri par rapidité
- Cause racine : CSS
overflow: hiddencréait un stacking context bloquant layout animations - Solution : Changement
overflow: hidden→overflow: visiblesur.team-cardet.team-card-header - Impact : Animations spring (300ms) maintenant visibles lors du réarrangement des équipes/joueurs
- Gestion du texte débordant : Conservée via
text-overflow: ellipsissur.team-name
server-go/web/src/components/TeamCard.css: 2 changements (lignes 10 et 34)server-go/config.json: Version bumped (2.44.1 → 2.44.2)
- ✅ Code review : CSS specificity et non-régression vérifiés
- ✅ QA : Animations testées, performances inchangées
- ✅ Breaking changes : Aucun
- Patch release (2.44.y) - correction mineure sans nouveau feature
- Backward compatible - aucun change API
- Frontend only - aucune modification backend
- [GamePage]: Tri équipes et joueurs par temps de réponse (feature tri-rapidite-reponse)
- Tri dynamique : Équipes et joueurs triés par temps de buzz (plus rapide en haut)
- Phase-aware : Tri actif UNIQUEMENT en STARTED/PAUSED/REVEALED (hors jeu = tri par score)
- Badges de classement : 🏆 (rang 1), 🥈 (rang 2), 🥉 (rang 3)
- Affichage temps : XXXms pour chaque équipe et joueur ayant buzzé
- Animation réorganisation : Spring transition ~300ms (stiffness: 300, damping: 30)
- Flash animation : Pulsation verte 500ms au nouveau buzz
- Équipes non-buzzées : Restent au bas de la liste sans badge ni temps
- Tri stable : Même temps de buzz conserve l'ordre original
- Responsive : Font-size adaptée (0.85rem desktop, 0.75rem tablet, 0.6-0.7rem mobile)
GamePage.jsx: Logic tri équipes (lines 63-97), useMemo optimizationGamePage.css: Styles.rank-badge,.team-response-timeTeamCard.jsx: Logique tri joueurs (lines 64-77), calcul temps ms (lines 50-52)TeamCard.jsx: Affichage badges (line 120) et temps (lines 123, 253-256)TeamCard.css: Styles.buzzer-response-time, animation@keyframes buzz-flashGamePage.test.jsx: 7 tests unitaires couvrant logique tri et calculstests/e2e/tri-rapidite-reponse.md: 12 scénarios E2E documentés
- Unit tests JS : 7 tests validant calcul temps, tri, badges, phase-aware
- E2E scenarios : 12 scénarios manuels (buzz équipes, joueurs, responsive, edge cases)
- Code review : APPROVED (Phase 3 complétée)
- QA validation : VALIDATED (Phase 4 complétée)
- Calcul temps :
(timestamp - gameTime) / 1000(µs → ms) - Dépendances : Aucune nouvelle dépendance (utilise Framer-Motion existant)
- Performance : Optimisé via useMemo + layoutId Framer-Motion
- Breaking changes : Aucun
- [Logs]: WebSocket dédiée
/ws/logspour une gestion optimisée des logs- Séparation des WebSockets :
/wspour le jeu,/ws/logspour les logs - Connexion directe : LogsPage se connecte à
/ws/logsau lieu de/ws - Messages dédiés : LOG_HISTORY (historique à la connexion), LOG_ENTRY (temps réel)
- Pas de conflit : Les logs ne transitent plus par la WebSocket de jeu
- Séparation des WebSockets :
- [LogsPage]: Utilise
connectToLogs()au lieu deconnect()- Hook personnalisé pour gérer la WebSocket
/ws/logs - Subscription/unsubscription automatique
- Hook personnalisé pour gérer la WebSocket
- [LogsPage]: Layout avec position fixed et scroll interne
- Page fixe sans scroll global (
.logs-page { position: fixed }) - Toolbar sticky en haut (
.logs-toolbar { position: sticky, z-index: 10 }) - Liste des logs scrollable (
.logs-list { flex: 1, overflow-y: auto })
- Page fixe sans scroll global (
websocket.go: Nouvelle fonctionServeLogsWS()pour/ws/logsmain.go: Handler/ws/logs,ConnectToLogs(),DisconnectFromLogs()LogsPage.jsx: HookuseLogsWebSocket()avec connexion dédiéeLogsPage.css: Structure flexbox avec position fixeduseWebSocket.js: Suppression handlers LOG_HISTORY et LOG_ENTRY (déplacés vers useLogsWebSocket)
- [Logs]: Page de visualisation des logs serveur en temps reel
- Route
/admin/logset/anim/logs: Nouvelle page d'administration - LogBuffer : Buffer circulaire thread-safe (capacite 1000 logs)
- BroadcastLogger : Logger avec diffusion temps reel via WebSocket
- Filtres de niveau : DEBUG (gris), INFO (blanc), WARN (orange), ERROR (rouge)
- Filtres de composant : App, Engine, HTTP, WebSocket, TCP, UDP
- Recherche temps reel : Debounce 300ms avec highlight des termes
- Auto-scroll intelligent : Pause automatique au scroll manuel, reprise en bas
- Indicateur nouveaux logs : Badge flottant cliquable pour descendre
- Export : Telechargement des logs filtres au format
.log
- Route
models.go: StructsLogLevel,LogComponent,LogEntrylogbuffer.go:LogBufferavecAdd(),GetAll(),GetRecent()logger.go:BroadcastLoggeravecDebug(),Info(),Warn(),Error()websocket.go:SubscribeToLogs(),UnsubscribeFromLogs(),BroadcastToLogSubscribers()messages.go: ActionsSUBSCRIBE_LOGS,UNSUBSCRIBE_LOGS,LOG_HISTORY,LOG_ENTRYmain.go: Handlers et integration du loggerLogsPage.jsx: Page principale avec toolbar et liste de logsLogEntry.jsx: Composant d'affichage d'une ligne de loguseWebSocket.js: HandlersLOG_HISTORY,LOG_ENTRY, fonctionssubscribeLogs,unsubscribeLogsNavbar.jsx: Lien "Logs" dans la section Config
logbuffer_test.go: Tests unitaires pour LogBuffer (Add, Circular, Concurrency, GetRecent)
- [VPlayer]: Interface complète de joueur virtuel avec affichage optimisé
- Page d'enrôlement
/: Formulaire d'inscription (pseudo 2-20 caractères)- Fond blanc pour meilleure lisibilité
- État d'attente si inscriptions fermées ("En attente de l'ouverture...")
- Reconnexion automatique si joueur déjà inscrit côté serveur
- Validation temps réel du pseudo
- Page VPlayer
/player: Interface responsive avec badges d'identité permanents- Layout en 4 zones : Timer (top), Question, Média (cliquable pour buzz), Réponses
- Zone média clickable pour buzzer (76% de largeur, centrée)
- Badges flottants non-intrusifs : Nom joueur (15%), Équipe (85%)
- Alignement précis horizontal avec les badges à hauteur du timer
- Détection de suppression : redirection automatique vers
/si admin supprime le joueur
- Bouton BUZZ intelligent : États visuels et retour haptique
- Phase STOPPED : "En attente de question" (gris, désactivé)
- Phase PREPARE : "Préparation..." (orange, désactivé)
- Phase READY/COUNTDOWN : "Prêt !" (cyan, désactivé)
- Phase STARTED : "BUZZ !" (vert pulsant, actif)
- Phase PAUSED : "Déjà buzzé" (bleu, désactivé)
- Vibration haptique au buzz (100ms si supporté)
- Feedback visuel de buzz : Overlay vert avec checkmark géant
- Bordure verte pulsante plein écran
- Animation checkmark (✓) avec pop-in
- Texte "BUZZÉ !" avec glow vert
- Disparition automatique après 1.5s
- QR Code sur
/tv: Overlay affiché quand l'enrollment est actif- QR Code 300x300px généré dynamiquement
- Barre de progression des joueurs inscrits
- Zone ENROLL dans
/anim/teams: Contrôles compacts sur 2 lignes- L1: "Places max: [10] Inscrits: 0/10"
- L2: Bouton "Lancer Inscriptions" / "Fin Inscriptions"
- Routes
/adminet/anim: Alias complets fonctionnels- Navbar avec préfixe dynamique selon l'URL courante
- Toutes les sous-routes fonctionnent avec les deux préfixes
- Page d'enrôlement
- [Engine]: Protection MEMORY contre buzz VPlayer
- Questions MEMORY ne peuvent pas être buzzées (contrôle exclusif admin)
ProcessButtonPress()ignore les buzz pour TYPE="MEMORY"- Test unitaire ajouté :
TestMemoryQuestionBuzzBlocking
- [Engine]: Correction REVEAL depuis PAUSED
- Permettre REVEAL depuis STOPPED ou PAUSED
- Arrêt propre des timers countdown et principal
- [Engine]: Amélioration
ClearBumpers()- Dissociation des bumpers dans les équipes (reset
team.Bumper) - Reset complet des statuts et temps d'équipes
- Dissociation des bumpers dans les équipes (reset
- [Engine]: Garantie champ team.NAME
SetTeams()remplit automatiquementteam.Namedepuis la clé si vide
- [UI]: Responsive VPlayer layout
- Container queries pour adaptation aux différentes tailles d'écran
- Badges redimensionnés dynamiquement (clamp)
- Zone média ajustée pour smartphones et tablettes
- [Routes]: Restructuration de l'architecture des routes pour clarté et cohérence
- Route
/: Page d'inscription joueurs (PlayerPage) - Routes
/admin/*: Pages d'administration (GamePage, Scores, Teams, Quiz, etc.) - Routes
/anim/*: Alias des routes admin (même comportement) - Route
/tv: Affichage TV plein écran
- Route
- [Navbar]: Correction de la détection active pour supporter les deux préfixes
- Fonction
isActiveRoute()pour vérifier les deux chemins - Renommage de l'onglet "Équipes" → "Joueurs"
- Fonction
- [TeamsPage]: Réorganisation de la carte joueur non assigné en 3 lignes
- Ligne 1 : Input nom + badge PRET + bouton suppression
- Ligne 2 : Pastille avatar + 4 boutons couleurs QCM + poignée de drag
- Ligne 3 : Informations techniques (adresse MAC + version)
- Bouton de suppression (×) avec confirmation
- [Tests]: Correction des tests unitaires liés à la phase COUNTDOWN
- Ajout de
StartImmediate()dans engine.go pour tester sans goroutines
- Ajout de
- Synchronisation compteur joueurs virtuels : Utilise
gameState.virtualPlayerCount(source serveur)
models.go: ChampsEnrollmentActive,ShowQRCode,IS_VIRTUAL,PhaseEnroll,VirtualPlayerCountengine.go:StartEnrollment(),StopEnrollment(),HandleVirtualPlayerConnect(),StartImmediate()protocol/messages.go: Actions SHOW_QR_CODE, HIDE_QR_CODE, PLAYER_CONNECT, PLAYER_CONNECTEDhttp.go: Ajout/admindans la liste des routes SPAApp.jsx: Routes/admin/*et/anim/*en aliasVPlayerPage.jsx: Layout 4 zones, badges permanents, zone média cliquableVPlayerPage.css: Positionnement badges (15%/85%), zone média 76%, responsive clampEnrollPage.jsx: Gestion état d'attente, reconnexion autoBuzzButton.jsx: Bouton avec états visuels et vibration haptiqueQRCodeOverlay.jsx: Overlay QR codeTeamsPage.jsx: Zone enrollment compacte, carte joueur 3 lignes, bouton suppressionNavbar.jsx: Préfixe dynamique/adminou/anim,isActiveRoute()PlayerDisplay.jsx: Badges permanents pour VPlayer
- Nouvelles images de fond festives : Remplacement des dégradés par des images plus joyeuses
- Confettis colorés sur fond noir
- Ballons dorés avec serpentins
- Traînées de lumières néon
- Images sourced from Unsplash (libres de droits)
-
Affichage TV - Phase PREPARATION : Nouveau design centré
- Texte "NOUVELLE QUESTION" remplace "PREPAREZ-VOUS"
- Catégorie masquée (affichée uniquement en phase PRÊT)
- Centrage parfait à l'écran
-
Affichage TV - Phase PRÊT (READY) : Affichage de la catégorie
- Icône de catégorie (grande) remplace l'icône main ✋
- Nom de catégorie avec fond coloré
- Animation pulsante
-
Affichage TV - Phase DÉCOMPTE (COUNTDOWN) : Animation de la catégorie
- La catégorie s'anime du centre vers la zone question
- Format inline : icône à gauche + nom avec fond coloré
- Applicable aux questions NORMAL, QCM et MEMORY
PlayerDisplay.jsx: Refonte des phases PREPARE, READY, COUNTDOWNPlayerDisplay.css: Nouveaux styles.prepare-state,.category-badge-inline,.category-badge-largeassets/demo/demo_bg_*.jpg: 3 nouvelles images de fond embarquées
- Mode Demo avec images embarquées : Les questions de démonstration incluent maintenant des images
demo1: Carte de l'Australie (question géographie)demo4: Chercheur d'or (question) + Tableau périodique (réponse)demo7: Pizza (question) + Carte de l'Italie (réponse)- Images téléchargées depuis Unsplash et embarquées dans l'exécutable
- Extraction automatique au premier lancement
- Layout des cartes questions : Réorganisation en 2 lignes pour plus de clarté
- Ligne 1 : Nom de la question + Badge statut (AVAILABLE, STARTED, STOPPED, REVEALED) + Bouton supprimer
- Ligne 2 : Catégorie + Type (Normal/QCM/MEMORY) + Target (Joueur/Équipe) + Temps + Points
- Badge "Normal" ajouté pour les questions standards (comme QCM et MEMORY)
- Le badge Target est maintenant toujours visible
QuestionCard.jsx: Header divisé enqcard-header-row1etqcard-header-row2QuestionCard.css: Styles pour les deux lignes + badge.qcard-normal-badgemain.go:createDemoQuestions()avec champs MEDIA, extraction depuisembed.FSassets/demo/: 5 images embarquées (demo1_australia, demo4_gold_miner, demo4_periodic_table, demo7_pizza, demo7_italy)
- Formulaire QCM : Les 4 réponses (A, B, C, D) s'affichent maintenant correctement dans la colonne de configuration
- Layout vertical (flex column) au lieu de grille 2x2
- Chaque réponse a un fond coloré correspondant à sa couleur (rouge/vert/jaune/bleu estompé)
- Résolution du conflit CSS entre
QuestionsPage.cssetPlayerDisplay.css - Classe renommée de
.qcm-answers-gridà.qcm-form-answerspour éviter les collisions
QuestionsPage.jsx: Classe CSS renommée pour éviter le conflitQuestionsPage.css: Layout flex column avec fond coloré par réponse- La réponse correcte garde sa couleur d'origine (pas de forçage en vert)
-
Procédures de développement : Workflow complet DEV → QUALIF → RELEASE
docs/DEV_PROCEDURE.md: Environnement, conventions, debuggingdocs/QUALIF_PROCEDURE.md: Tests, checklists, rapport de qualificationdocs/RELEASE_PROCEDURE.md: 15 étapes pour mise en production- Scripts de build :
build-release.ps1etbuild-release.sh
-
README.md : Présentation complète du projet
- Fonctionnalités, installation, architecture
- Guide de démarrage rapide
- Liens vers toute la documentation
-
Navbar réorganisée : 2 zones distinctes
- Zone Jeu (fond bleu) : Jeu, Scores, Palmarès, Historique
- Zone Config (fond gris) : Équipes, Questions, Config
- Labels verticaux pour identifier chaque zone
- CLAUDE.md simplifié : Références vers les procédures au lieu de duplication
- Version unique : Plus de version web séparée (bundle complet)
Navbar.jsx: Structure en 2 groupes avecnav-group-gameetnav-group-configNavbar.css: Styles des zones avec dégradés et labels verticaux
-
Exécutable portable : Les fichiers web sont embarqués dans le binaire Go
- Utilise
//go:embedpour inclureweb/dist/dans l'exécutable - Taille finale : ~13 MB (exécutable autonome)
- Aucune dépendance externe pour l'interface web
- Mode portable prioritaire sur le mode filesystem
- Utilise
-
Scripts de build : Automatisation du build portable
build.ps1: Script PowerShell pour Windowsbuild.sh: Script Bash pour Linux/macOS- Étapes : build frontend → copie dist → build Go
-
Structure de données portable :
- Données dans
./data/à côté de l'exécutable data/config/: Configuration (teams, bumpers, history)data/files/: Fichiers utilisateur (questions, backgrounds)
- Données dans
cmd/server/embed.go: Directive//go:embed all:distinternal/server/http.go: Supportfs.FSpour fichiers embarqués- Fallback automatique : embedded → filesystem → legacy
cmd/server/embed.go: Embedding des fichiers webinternal/server/http.go:SetEmbeddedFS(), handlers modifiéscmd/server/main.go: Détection mode embeddedbuild.ps1,build.sh: Scripts de build
-
Vue PALMARES TV : Classement des équipes et joueurs par catégorie sur l'affichage TV
- Nouvelle vue accessible depuis le bouton "Palmares" dans les contrôles TV de l'admin
- Grille 3x2 fixe avec maximum 6 catégories (affichage statique, pas de scroll)
- Chaque carte catégorie affiche : icône, nom, total points (équipes + joueurs)
- Classement séparé Équipes et Joueurs avec médailles 🥇🥈🥉
- Mise en évidence des vainqueurs (rank-1) avec effet doré lumineux
-
Page admin Palmares : Route
/palmaresdans la navbar- Vue collapsible par catégorie avec boutons "Tout ouvrir/fermer"
- Résumé des points par catégorie
- Composant Podium compact pour le top 3
- Fetch
/historypour agréger les points par catégorie - Séparation stricte TEAM vs PLAYER (pas de mélange)
- Calcul des rangs avec gestion des égalités
- CSS viewport-based pour l'affichage TV statique
CategoryPalmaresPage.jsx: Page admin PalmaresCategoryPalmaresPage.css: Styles page adminPlayerDisplay.jsx: Vue PALMARES TV avec fetch history et aggregationPlayerDisplay.css: Styles grille 3x2 et highlighting vainqueursGamePage.jsx: Bouton "Palmares" dans contrôles TVApp.jsx: Route/palmaresNavbar.jsx: Lien navigation "Palmares"
-
Animation cascade pour Memory : Les cartes se retournent une par une pendant la phase COUNTDOWN
- Cascade reveal : cartes se révèlent avec 200ms de délai entre chaque (1→2→3→...→N)
- Décompte visuel : affiché seulement quand toutes les cartes sont révélées (5...4...3...2...1)
- Cascade hide : cartes se cachent immédiatement quand le décompte atteint 0
- Transition automatique vers STARTED quand toutes les cartes sont cachées
-
Synchronisation backend/frontend : Le backend calcule la durée totale de la phase COUNTDOWN
- Durée = cascade_reveal + MEMORIZE_TIME + cascade_hide
- Le frontend gère les animations localement avec des états dédiés
-
Calcul des points Memory : Score dynamique basé sur les paires trouvées et erreurs
- Formule :
Score = (paires_trouvées × POINTS_PER_PAIR) + COMPLETION_BONUS - (erreurs × ERROR_PENALTY) - Backend :
CalculateMemoryScore()dans engine.go - Frontend :
memoryScoreuseMemo dans GamePage.jsx - Score minimum = 0 (pas de score négatif)
- Formule :
-
Interface admin Memory :
- Zone Points : Affiche le score total calculé (readonly) avec tooltip détaillé
- Zone Affichage TV : Compteur paires (X/Y) et erreurs
- Attribution des points : Clic sur équipe/joueur attribue le score calculé
-
QuestionCard Memory :
- Points affichés = total maximum possible (paires × points_par_paire + bonus)
- Zone média remplacée par 2 slots de configuration :
- Slot gauche :
+X / paire(gradient violet) - Slot droit :
-Y / erreur(rouge si pénalité, gris sinon)
- Slot gauche :
- Badge "MEMORY" violet/rose
- MEMORY_CONFIG : Toutes les durées sont maintenant en secondes (plus de mix ms/s)
FLIP_DELAY: 3s (avant: 3000ms)REVEAL_DELAY: 0.5s (avant: 500ms)MEMORIZE_TIME: 5s (temps du décompte visuel)POINTS_PER_PAIR: 10 (points par paire trouvée)ERROR_PENALTY: 0 (pénalité par erreur)COMPLETION_BONUS: 0 (bonus si toutes les paires trouvées)
cascadeRevealDone: true quand toutes les cartes sont révéléeslocalCountdown: décompte indépendant du backend, démarre après cascade revealcascadeHideStarted: true quand la cascade hide est déclenchée (localCountdown === 0)cascadeHideDone: true quand toutes les cartes sont cachées
STAGGER_DELAY = 200ms // délai entre chaque carte
FLIP_ANIMATION = 600ms // durée de l'animation flipengine.go: Calcul de la durée totale COUNTDOWN +CalculateMemoryScore()models.go: FlipDelay et RevealDelay en float64 (secondes)PlayerDisplay.jsx: États et effets pour les animations cascadeGamePage.jsx:memoryScoreuseMemo, attribution des points MemoryGamePage.css: Style.memory-score-input,.memory-admin-statsQuestionCard.jsx: Affichage config Memory au lieu des imagesQuestionCard.css: Styles.qcard-memory-config-slotQuestionsPage.jsx: UI config en secondesCLAUDE.md: Documentation complète Memory
-
Cartes équipes - largeur : Les cartes équipes s'adaptent maintenant à la largeur de la colonne
- Problème : TeamsPage.css définissait
.teams-grid { display: grid; minmax(300px, 1fr) }qui forçait une largeur minimale de 300px - Solution : Sélecteur plus spécifique
.game-page .teams-grid { display: flex }dans GamePage.css
- Problème : TeamsPage.css définissait
-
Cartes équipes - joueurs visibles : Tous les joueurs sont maintenant affichés dans les cartes équipes
- Problème :
.team-card { overflow: hidden }coupait le contenu débordant - Solution :
overflow: visibleetflex-shrink: 0sur.game-page .team-card
- Problème :
-
Preview TV - hauteur alignée : La zone de preview TV a maintenant la même hauteur que les colonnes Questions et Équipes
- Problème :
aspect-ratio: 16/9etmax-heightcontraignaient la hauteur du preview - Solution :
height: 100%sur.tv-previewetalign-items: stretchsur le container
- Problème :
- Utilisation de sélecteurs CSS spécifiques (
.game-page .class) pour éviter les conflits entre pages - Les règles
!importantsurdisplay,visibilityetheightgarantissent l'affichage des joueurs
GamePage.css: Sélecteurs spécifiques.game-page .teams-grid,.game-page .team-cardQuestionPreview.css: Suppressionaspect-ratio: 16/9, ajoutheight: 100%CLAUDE.md: Documentation de la section "CSS Specificity & Layout Fixes"
- Synchronisation des images de fond : Tous les écrans TV affichent la même image simultanément
- Le serveur maintient
CurrentBackgroundIndexdans GameState - Goroutine de cycling basée sur la durée de chaque image
- Broadcast
BACKGROUND_CHANGEà tous les clients à chaque transition - Les clients utilisent l'index serveur au lieu du cycling local
- Transitions parfaitement synchronisées entre tous les écrans
- Le serveur maintient
engine.go: MéthodesGetCurrentBackgroundIndex(),SetCurrentBackgroundIndex(),NextBackground(),GetCurrentBackgroundDuration()models.go: ChampCurrentBackgroundIndexdans GameStatemessages.go: ActionBACKGROUND_CHANGE,BackgroundChangePayloadmain.go: GoroutinestartBackgroundCycling(),broadcastBackgroundChange()useWebSocket.js: HandlerBACKGROUND_CHANGE, statecurrentBackgroundIndexPlayerDisplay.jsx: UtilisegameState.currentBackgroundIndex
-
Décompte 3-2-1 avant le timer : Phase COUNTDOWN distincte
- Affichage visuel "3... 2... 1... GO!" avant le timer principal
- Nouvelle phase
COUNTDOWNdans la machine d'états - Badge orange "DECOMPTE" dans le Timer
- Les buzzers restent bloqués pendant le décompte
- Le timer démarre automatiquement après le décompte
-
Comportement QCM amélioré :
- READY : Zones de couleur sans texte de réponse
- COUNTDOWN : Texte des réponses apparaît avec animation
- STARTED : Question et médias affichés
engine.go: PhaseCOUNTDOWN, callbackOnCountdownTickmodels.go:PhaseCountdown,CountdownTimedans GameStatemain.go:broadcastCountdownUpdate(), gestion START avec countdownTimer.jsx: Badge "DECOMPTE", affichage du compteurPlayerDisplay.jsx: États COUNTDOWN, animation texte QCMuseWebSocket.js: HandlercountdownTime
-
Feedback visuel PONG : Indication claire de l'état de préparation des joueurs
- Équipes grisées (opacity 60%, grayscale 50%) en attendant que tous les joueurs répondent
- Badge compteur "X/Y" (ex: "1/3") indiquant joueurs prêts / total au lieu de "..."
- Joueurs individuels grisés jusqu'à leur réponse PONG
- Joueurs ayant répondu retrouvent leur couleur d'équipe avec bordure colorée
- Bordure d'équipe pointillée en attente, solide quand prête
-
Simulation PONG (debug) : Ctrl+clic sur un joueur en phase PREPARE simule une réponse PONG
- Fusion handlePong : Les handlers TCP et WebSocket fusionnés en une seule fonction
- ID bumper extrait du payload si présent (WebSocket), sinon utilise clientID (TCP)
- Suppression du code dupliqué
handleSimulatedPong
main.go: RefactoringhandlePong()unifiéTeamCard.jsx: CompteurreadyBuzzersCount/totalBuzzersCount, classewaiting-pongTeamCard.css: Styles.team-card.waiting,.waiting-pong,.waiting-pong.readyuseWebSocket.js: FonctionsimulatePong()GamePage.jsx: Gestion Ctrl+clic pour simuler PONG
-
CategoryBalance Component : Visualisation de l'équilibre des catégories sur la page Questions
- Barres divergentes par catégorie (questions et points)
- Zéro au centre = moyenne, droite = excès, gauche = manque
- Code couleur : vert (≤25%), orange (25-50%), rouge (>50%)
- Tooltip au survol avec détails complets
- Seules les catégories représentées sont affichées
- Animation framer-motion à l'entrée
-
Catégorie dans l'historique : Badge catégorie sur chaque groupe de question
- Ajout du champ
QuestionCategoryau modèleGameEvent - Icône colorée dans le header de chaque groupe
- Visible dans la vue réduite et détaillée
- Ajout du champ
- Fix sélection de question : Correction de l'erreur JSON unmarshal
- Les questions de test avaient POINTS/TIME en nombres au lieu de strings
- La sélection depuis PREPARE/READY fonctionne maintenant correctement
components/CategoryBalance.jsx: Nouveau composantcomponents/CategoryBalance.css: Styles des barres divergentespages/QuestionsPage.jsx: Intégration du composantpages/HistoryPage.jsx: Import CATEGORIES, affichage badge catégoriepages/HistoryPage.css: Style.group-categoryinternal/game/models.go: ChampQuestionCategorydansGameEventcmd/server/main.go: Fix POINTS/TIME strings, catégorie dans événements
-
Persistance des données : Sauvegarde automatique sur disque
data/config/teams.json: Équipes avec scores et TeamPointsdata/config/bumpers.json: Joueurs avec scores et assignationsdata/config/history.json: Historique des événements (source de vérité)- Auto-save asynchrone après chaque modification
- Chargement automatique au démarrage
-
Event Sourcing : L'historique est la source de vérité pour les scores
RecalculateScoresFromHistory(): Recalcule tous les scores depuis les événements- Les scores peuvent être entièrement reconstruits à tout moment
-
Backup sélectif (
/backup-select) : Choisir quoi sauvegarder- Paramètres :
questions,teams,bumpers,history,backgrounds - Exemple :
/backup-select?questions=true&history=true
- Paramètres :
-
Reset sélectif (
/reset-select) : Choisir quoi réinitialiser- Paramètres :
all,questions,teams,bumpers,history,backgrounds - Exemple :
/reset-select?history=true&bumpers=true
- Paramètres :
-
Restore intelligent (
/restore) : Détection automatique du contenu TAR- Détecte les fichiers présents dans l'archive
- Restaure uniquement les éléments détectés
- Recharge les données dans l'engine après restauration
-
Interface ConfigPage : Sélecteurs pour backup et reset
- Section Sauvegarde : 5 cases à cocher (Questions, Équipes, Joueurs, Historique, Fonds)
- Section Réinitialisation : 5 cases à cocher avec confirmation
- Boutons Sauvegarder/Restaurer/Réinitialiser
- Nouveau fichier
docs/ADMIN_GUIDE.md: Guide d'administration complet- Persistance des données
- Sauvegarde et restauration
- Réinitialisation sélective
- Gestion des scores
- Historique des événements
engine.go: SaveTeams/LoadTeams, SaveBumpers/LoadBumpers, SaveHistory/LoadHistoryhttp.go: handleBackupSelect, handleResetSelect, handleRestore (intelligent)main.go: Configuration des chemins de persistanceConfigPage.jsx: UI pour backup/reset sélectifConfigPage.css: Styles pour les sections checkboxdocs/ADMIN_GUIDE.md: Guide d'administration
- History Page : Nouvelle page
/history-pagepour visualiser l'historique des points attribués- Endpoint API
GET /historyretournant[]GameEvent - Événements groupés par question (ordre chronologique)
- Vue collapsible : clic sur l'en-tête pour ouvrir/fermer
- Boutons "Tout ouvrir" / "Tout fermer"
- Vue réduite : Résumé des points par équipe et par joueur (badges colorés)
- Vue détaillée : Tableau avec Heure, Équipe, Joueur, Temps, Points
- Séparation stricte : points TEAM vs points PLAYER (pas de cumul mixte)
- Endpoint API
HistoryPage.jsx,HistoryPage.cssengine.go:AddGameEvent()models.go:GameEvent
-
Question Cards Layout : Nouvelle mise en page des cartes questions dans le panneau admin
- Layout horizontal : Thumbnail (70x70px) à gauche, texte à droite
- Header :
#ID [target] 30s 1pt [STATUS] - Body : Question (4 lignes max), Réponse (3 lignes max)
-
POINTS_TARGET : Système d'attribution des points par question
- Champ
POINTS_TARGETsur chaque question (PLAYERouTEAM) - Défaut :
PLAYERpour NORMAL,TEAMpour QCM - Indicateur admin avec badge coloré
- Champ
-
Un seul buzz par équipe : Premier joueur à buzzer représente l'équipe
- Si
team.Time > 0, les buzzes suivants sont ignorés
- Si
engine.go:ProcessButtonPress()GamePage.jsx,GamePage.css
- Points équipe indépendants : Nouveau champ
TEAM_POINTSsur les équipes- Score total = TEAM_POINTS + sum(player scores)
- Clic sur header équipe = points à l'équipe
- Clic sur ligne joueur = points au joueur
- Tooltip affichant la décomposition du score
models.go:Team.TeamPointsTeamCard.jsx,TeamCard.css
-
Layout page admin : Page fixe sans scroll global
- Scroll interne par colonne (Questions, Contrôles, Équipes)
- Alignement avec le bas de la preview TV
-
TeamCard optimisé : Réduction de l'espace occupé
- Score compact sans label
- Espacement et police réduits
- Pastilles d'équipes sur réponses QCM (phases STOPPED/REVEALED)
- Couleur = couleur de l'équipe
- Disposition horizontale, alignée à droite
- Taille dégradée : 70% (première) à 40% (dernière)
- Tri par temps de réponse
PlayerDisplay.jsx:teamsByQcmAnswerPlayerDisplay.css:.qcm-team-badges
- MEDIA_ANSWER : Support des images de réponse distinctes
MEDIA: Image affichée pendant STARTED/PAUSEDMEDIA_ANSWER: Remplace MEDIA pendant REVEALED- Effet visuel : Cadre vert pulsant autour de l'image de réponse
- Thumbnails sur les cartes questions
models.go:Question.MediaAnswerhttp.go:POST /questionsPlayerDisplay.jsx,PlayerDisplay.cssQuestionsPage.jsx,QuestionsPage.cssGamePage.jsx,GamePage.css
-
Points Animation : Animation visuelle quand des points sont ajoutés
- Confetti avec couleur d'équipe
- Animation flottante "+X pts" au centre
- Animation scale sur la ligne joueur
-
Debug Features :
- Ctrl+clic sur joueur : Simule un appui buzzer
- Ctrl+clic sur question : Force l'état READY
-
Waiting States : États visuels pour équipes/joueurs
- Grisés pendant PREPARE/READY jusqu'au PONG
- Grisés pendant STARTED/PAUSED jusqu'au buzz
-
Reaction Time : Affichage du temps de réaction
- Tri des joueurs par temps de réponse
GamePage.jsx,GamePage.cssTeamCard.jsx,TeamCard.cssengine.go:GameTime
-
Layout 4 zones pour l'affichage TV (/tv) :
- Zone 1 - Timer : 100px hauteur fixe
- Zone 2 - Question : 80px hauteur fixe
- Zone 3 - Media : flex: 1 (remplit l'espace)
- Zone 4 - Answers : 120px hauteur fixe, margin-top: auto
-
Timer couleur synchronisée : Couleur = couleur de la barre de progression
- Vert (> 50%), Orange (25-50%), Rouge (< 25%)
-
Transition QCM unifiée : Pas de re-render/flash entre READY → STARTED → REVEALED
PlayerDisplay.jsx,PlayerDisplay.css
- QuestionPreview : Simplifié en iframe vers
/tv- ~15 lignes de code vs 290
- Synchronisation parfaite avec l'affichage réel
- Zero maintenance
- Pastilles colorées indiquant l'état du jeu dans le Timer :
- ARRET (rouge), PREPARATION (orange), PRET (cyan)
- EN COURS (vert), PAUSE (bleu), REPONSE (gris)
Timer.jsx,Timer.css
- Drag and drop pour reordonner les questions
- Poignée ⋮⋮ sur chaque carte
- Feedback visuel pendant le drag
- Champ
ORDERpersisté dansquestion.json - Action WebSocket
REORDER_QUESTIONS
messages.go:ReorderQuestionsPayloadmain.go:handleReorderQuestionsQuestionsPage.jsx,QuestionsPage.cssGamePage.jsx
- Support QCM : Questions à choix multiples
- Types :
NORMALouQCM - 4 réponses colorées (Rouge A, Vert B, Jaune C, Bleu D)
- Champ
QCM_CORRECTpour la bonne réponse - Badge "QCM" dans la liste des questions
- Types :
TYPE,QCM_ANSWERS,QCM_CORRECT
models.go:QuestionType, QCMAnswershttp.go:POST /questionsQuestionsPage.jsx,QuestionsPage.css
-
Teams Page Drag & Drop : Glisser-déposer pour assigner les joueurs aux équipes
- Grille des équipes à gauche
- Joueurs non assignés à droite
-
Couleurs de réponse : Chaque joueur peut avoir une couleur QCM
- Rouge (A), Vert (B), Jaune (C), Bleu (D)
- Sélection uniquement quand non assigné à une équipe
- Champ
ANSWER_COLORdans le modèle Bumper
TeamsPage.jsx,TeamsPage.cssmodels.go:Bumper.AnswerColor
- Podium : Composant partagé pour les classements
- Variantes :
default(full size),compact(preview) - Gestion des égalités (même rang partagé)
- Utilisé par : ScoresPage, PlayerDisplay, QuestionPreview
- Variantes :
Podium.jsx,Podium.css
-
Structure des pages :
/GamePage (admin)/tvPlayerDisplay/scoreboardScoresPage/teamsTeamsPage/quizQuizPage/settingsSettingsPage
-
Layout 3 colonnes pour GamePage (admin)
-
Statuts de questions colorés : AVAILABLE (vert), STARTED (orange), STOPPED (rouge), REVEALED (gris)
- Migration ESP32 → Go : Serveur Go sur Raspberry Pi
- Rétrocompatibilité : Support TCP + UDP pour BuzzClick v1
- Fonctionnalités complètes :
- HTTP server (port 80)
- WebSocket server (/ws)
- TCP server (port 1234)
- UDP broadcast (port 1234)
- DNS server (port 53) - captive portal
- mDNS (_sock._tcp)
- Questions CRUD
- Teams/Bumpers management
- Game state machine
- TAR backup/restore
- Configuration JSON
cmd/server/main.gointernal/game/engine.go,models.gointernal/server/http.go,websocket.go,tcp.go,udp.gointernal/protocol/messages.go,parser.go