Files
segnapunti/CLAUDE.md
T

6.6 KiB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Scopo del progetto

Segnapunti Anto è una PWA per il segnapunti pallavolo in tempo reale. Un server Express/WebSocket gestisce lo stato di gioco; due interfacce Vue separate — display (tabellone pubblico) e controller (pannello operatore) — si sincronizzano via WebSocket.

Comandi

npm run dev          # Vite dev server — display: :5173/display, controller: :5173/controller
npm run serve        # Build + avvio produzione — display: :3000/display, controller: :3000/controller

npm run test         # Vitest in modalità watch
npm run test:all     # Tutte le suite Vitest in una sola esecuzione
npm run test:unit    # Solo unit + integration
npm run test:component  # Test componenti Vue (happy-dom)
npm run test:stress  # Load test (50+ client concorrenti)
npm run test:e2e     # Playwright E2E (richiede npm run serve attivo)
npm run test:e2e:ui  # Playwright con UI interattiva

Per eseguire un singolo file di test: npx vitest run tests/unit/gameState.test.js

Architettura

Controller (Vue)  ──WebSocket──┐
Display (Vue)     ──WebSocket──┤── websocket-handler.js ── gameState.js
                               │         │
                               │         └── persist.js ── .segnapunti/state.json

Tutta la logica di gioco è in src/gameState.js come funzioni pure esportate:

  • createInitialState() — restituisce lo stato iniziale
  • applyAction(state, action) — reducer immutabile (deep-clone via structuredClone)
  • checkVittoria(state) — condizioni di vittoria set (25 punti, vantaggio di 2; 15 punti nel set decisivo)
  • punteggio / setVinti / servizio — derivano punteggio corrente, set vinti e chi serve dalla striscia (lo stato NON memorizza questi valori direttamente)

Punteggio, set e servizio si ricavano sempre dalla striscia: al primo punto di ogni set applyAction('incPunt') salva in formInizio uno snapshot della formazione di partenza (usato dal referto); decPunt lo rimuove se il set torna a 0-0.

src/websocket-handler.js riceve i messaggi WebSocket, valida che il mittente sia un controller (non un display), chiama applyAction, fa il broadcast del nuovo stato a tutti i client, poi invoca onStateChange per persistere su disco.

src/wsMixin.js è il mixin Vue lato client: gestisce connessione/riconnessione WebSocket (backoff esponenziale) ed espone i computed punt/servHome/set.

src/referto.js genera il referto di gara: buildRefertoHtml(state, now) è una funzione pura che restituisce l'HTML (testabile), mentre generaReferto(state) è il wrapper che apre la finestra con window.open e avvia la stampa.

src/persist.js carica/salva lo stato su .segnapunti/state.json. src/server-utils.js ricava gli IP di rete (getNetworkIPs, con sorgente iniettabile) e stampa gli URL all'avvio. src/spot-utils.js espone listSpotVideos(spotDir), che elenca i .mp4 (ordinati) della cartella spot/; ritorna [] se la cartella manca.

Time out / spot video — l'action toggleTimeout commuta il flag booleano state.timeout. Quando attivo, il display mostra a tutto schermo i video .mp4 della cartella spot/ nella root (non versionata): uno solo va in loop, più video girano in sequenza ripartendo dal primo; se la cartella è vuota mostra un overlay "TIME OUT". La lista è esposta dall'endpoint HTTP /spot/list (JSON) e i file sono serviti staticamente da /spot/<file>.mp4, sia in produzione (server.js) sia in sviluppo (vite-plugin-websocket.js). I video partono con audio; se il browser blocca l'autoplay con suono si ripiega automaticamente su muto — per avere l'audio reale il browser del display (schermo senza input) va avviato in kiosk con autoplay consentito (es. Chrome --autoplay-policy=no-user-gesture-required).

server.js (Express) espone createApp(distDir) (solo routing, testabile) e startServer(port) (HTTP + WebSocket); serve entrambe le interfacce sulla porta 3000: /displaydist/index.html, /controllerdist/controller.html. Singolo endpoint WebSocket su /ws. L'avvio automatico parte solo se il file è eseguito come entrypoint.

In sviluppo, vite-plugin-websocket.js incorpora il server WebSocket dentro il dev server Vite con middleware di URL rewrite per /display e /controller.

Vincoli di design

  • Tutta la logica sul server — i client sono pura UI; il server è l'unica source of truth.
  • WebSocket role-based — i client si registrano come display o controller; solo i controller possono inviare azioni.
  • Stato immutabileapplyAction non muta mai lo stato, restituisce sempre un nuovo oggetto.
  • Un solo controller — il design prevede un controller e un display attivi; non esiste conflict resolution per controller simultanei.
  • Stato persistentesrc/persist.js salva lo stato in .segnapunti/state.json dopo ogni azione e lo carica all'avvio del server.

Layout dei test

Suite Percorso Runner
Unit tests/unit/ Vitest + Node
Integration tests/integration/ Vitest + Node
Component tests/component/ Vitest + Happy-DOM
Stress tests/stress/ Vitest + Node
E2E tests/e2e/ Playwright (Chromium, Firefox, Mobile Chrome)

I test E2E girano in serie (workers: 1) per evitare race condition sullo stato WebSocket. Eseguire npm run serve prima di npm run test:e2e.

Deploy

Il progetto si distribuisce tramite Docker. L'immagine è pubblicata sul registry Gitea self-hosted (santantonio.sytes.net):

docker compose up -d   # Avvia con immagine dal registry privato (porta 3000)

Il volume ./.segnapunti persiste lo stato tra i riavvii del container.

Istruzioni operative

  • Per ogni nuova funzionalità: analizza come si inserisce nel flusso action → applyAction → broadcast, poi decidi se aggiungere un nuovo tipo di action o estendere uno esistente.
  • Qualsiasi modifica alle regole di gioco va fatta esclusivamente in src/gameState.js.
  • Qualsiasi modifica al protocollo WebSocket va fatta in src/websocket-handler.js.
  • Aggiungere test unit in tests/unit/gameState.test.js per ogni nuovo action type o regola di gioco.
  • Il codice deve essere scritto in italiano per commenti e nomi di variabili di dominio (es. servHome, striscia, nomi), ma in inglese per nomi tecnici standard (state, action, handler).
  • Non inserire emoji in codice, UI (label, testi dei bottoni, banner) o commenti, a meno che l'utente non le richieda esplicitamente.