f65f87722c
- README: sezione Funzionalità riscritta per spiegare in modo completo display e controller (layout, punteggio/set, config, cambi, servizio, voce, time out/spot, reset/referto, regole partita e amichevole). - CLAUDE.md: descrizione di toggleTimeout, flag `timeout`, cartella `spot/`, endpoint `/spot/list` e nota sull'autoplay con audio in kiosk.
94 lines
6.4 KiB
Markdown
94 lines
6.4 KiB
Markdown
# 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
|
|
|
|
```bash
|
|
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: `/display` → `dist/index.html`, `/controller` → `dist/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 immutabile** — `applyAction` 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 persistente** — `src/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`):
|
|
|
|
```bash
|
|
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`).
|