From 34c9a61e798565b60a7ae60b6e13667a0b4a5fc5 Mon Sep 17 00:00:00 2001 From: Davide Grilli Date: Wed, 8 Jul 2026 00:24:15 +0200 Subject: [PATCH] docs(claude): comprimi CLAUDE.md per ridurre i token senza perdere contenuti MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Riscrittura più concisa (elenchi puntati al posto di prosa, frasi accorciate); tutti i concetti tecnici restano invariati. --- CLAUDE.md | 87 ++++++++++++++++++++++++++++--------------------------- 1 file changed, 44 insertions(+), 43 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 39c377f..27e36d4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,70 +1,72 @@ # CLAUDE.md -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. +Guida per Claude Code su questo 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. +**Segnapunti Anto**: PWA segnapunti pallavolo in tempo reale. Server Express/WebSocket gestisce lo stato di gioco; due interfacce Vue — **display** (tabellone pubblico) e **controller** (pannello operatore) — sincronizzate 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 dev # Vite dev — 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 +npm run test # Vitest watch +npm run test:all # Tutte le suite in una sola esecuzione +npm run test:unit # Solo unit + integration +npm run test:component # 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` +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 + │ │ + │ └── persist.js ── .segnapunti/state.json ``` -**Tutta la logica di gioco** è in `src/gameState.js` come funzioni pure esportate: -- `createInitialState()` — restituisce lo stato iniziale +**`src/gameState.js`** — logica di gioco, funzioni pure: +- `createInitialState()` — 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) +- `checkVittoria(state)` — vittoria set: 25 punti/vantaggio 2 (15 nel set decisivo) +- `punteggio` / `setVinti` / `servizio` — derivati dalla `striscia`, NON memorizzati direttamente nello stato -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. +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. -Ogni voce della `striscia` accumula anche lo storico di cambi e time out del set, popolato lazy (nessun campo se il set non ha eventi), senza timestamp — il "quando" è il punteggio al momento dell'evento, non l'orologio: -- `set.timeouts`: array di `{ team, punteggio: { home, guest } }`, appeso da `startTimeout` (solo se il time out non è già attivo, per non duplicare l'evento su un restart). -- `set.cambi`: array di `{ team, in, out, punteggio }`, uno per ogni sostituzione effettivamente applicata da `confermaCambi` (nulla viene registrato se la validazione del cambio fallisce). +Ogni voce della `striscia` accumula anche lo storico di cambi/time out del set, popolato lazy (nessun campo se il set non ha eventi), senza timestamp — il "quando" è il punteggio al momento dell'evento, non l'orologio: +- `set.timeouts`: `{ team, punteggio: { home, guest } }`, appeso da `startTimeout` (solo se non già attivo, per non duplicare l'evento su un restart). +- `set.cambi`: `{ team, in, out, punteggio }`, uno per sostituzione applicata da `confermaCambi` (nulla se la validazione fallisce). -`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/websocket-handler.js`** — riceve i messaggi, valida che il mittente sia `controller` (non `display`), chiama `applyAction`, fa broadcast del nuovo stato, poi invoca `onStateChange` per persistere. -`src/wsMixin.js` è il mixin Vue lato client: gestisce connessione/riconnessione WebSocket (backoff esponenziale) ed espone i computed `punt`/`servHome`/`set`. +**`src/wsMixin.js`** — mixin Vue lato client: connessione/riconnessione WebSocket (backoff esponenziale), 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. Per ogni set, oltre alla formazione di partenza e alla griglia punti, mostra (se presenti) l'elenco di `set.timeouts` e `set.cambi` ordinato per punteggio crescente. +**`src/referto.js`** — genera il referto di gara: `buildRefertoHtml(state, now)` è pura (testabile, ritorna HTML); `generaReferto(state)` apre la finestra (`window.open`) e avvia la stampa. Per ogni set mostra formazione di partenza, griglia punti e (se presenti) `set.timeouts`/`set.cambi` ordinati per punteggio crescente. -`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. +**`src/persist.js`** — carica/salva lo stato su `.segnapunti/state.json`. +**`src/server-utils.js`** — `getNetworkIPs` (sorgente iniettabile), stampa gli URL all'avvio. +**`src/spot-utils.js`** — `listSpotVideos(spotDir)`: elenca i `.mp4` (ordinati) di `spot/`; `[]` se la cartella manca. -**Time out / spot video** — le action `startTimeout` (richiede `team`, `video` opzionale) e `stopTimeout` impostano/azzerano `state.timeout`, `timeoutTeam`, `timeoutVideo` e `timeoutStartedAt`. Il timestamp `startedAt` lo aggiunge `websocket-handler.js` (non il client), così `gameState.js` resta puro e deterministico; lo stesso handler arma un timer server-side di 30 secondi che invia automaticamente `stopTimeout` (sopravvive a refresh o disconnessione del controller). Sul controller il pulsante Time Out apre una modal per scegliere la squadra e uno dei video `.mp4` della cartella `spot/` nella root (non versionata), oppure nessun video; durante il time out il pulsante mostra il countdown e ripremerlo chiude in anticipo. Il display copre il tabellone con un overlay a tutto schermo con countdown: riproduce il video scelto (riallineandosi al punto giusto in caso di riconnessione a metà time out) e ripiega sulla scritta "TIME OUT " se non c'è video o se il video finisce prima dei 30 secondi, con dissolvenza nell'ultimo secondo. La lista è esposta dall'endpoint HTTP `/spot/list` (JSON) e i file sono serviti staticamente da `/spot/.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`). +**Time out / spot video** — `startTimeout` (`team`, `video` opzionale) e `stopTimeout` impostano/azzerano `state.timeout`, `timeoutTeam`, `timeoutVideo`, `timeoutStartedAt`. `startedAt` è aggiunto da `websocket-handler.js` (non dal client), così `gameState.js` resta puro/deterministico; lo stesso handler arma un timer server-side di 30s che invia automaticamente `stopTimeout` (sopravvive a refresh/disconnessione del controller). Sul controller, il pulsante Time Out apre una modal per scegliere squadra + un `.mp4` da `spot/` (cartella in root, non versionata) o nessun video; durante il time out mostra il countdown, ripremerlo chiude in anticipo. Sul display, un overlay fullscreen con countdown copre il tabellone: riproduce il video (riallineandosi al punto giusto su riconnessione a metà time out) o mostra "TIME OUT " se manca il video o finisce prima dei 30s, con dissolvenza nell'ultimo secondo. Lista esposta da `/spot/list` (JSON), file serviti da `/spot/.mp4` sia in produzione (`server.js`) che in dev (`vite-plugin-websocket.js`). I video partono con audio; se l'autoplay con suono è bloccato si ripiega su muto — per audio reale il browser del display 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. +**`server.js`** — Express: espone `createApp(distDir)` (solo routing, testabile) e `startServer(port)` (HTTP + WebSocket). Serve `/display` → `dist/index.html`, `/controller` → `dist/controller.html` sulla porta 3000; endpoint WebSocket unico `/ws`. Si auto-avvia solo se 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`. +**`vite-plugin-websocket.js`** — in dev, incorpora il server WebSocket nel dev server Vite con rewrite URL 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. +- **Logica solo server-side** — 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 inviano 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. +- **Un solo controller/display attivi** — nessuna conflict resolution per controller simultanei. +- **Stato persistente** — `src/persist.js` salva in `.segnapunti/state.json` dopo ogni azione e ricarica all'avvio. ## Layout dei test @@ -76,25 +78,24 @@ In sviluppo, `vite-plugin-websocket.js` incorpora il server WebSocket dentro il | 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`. +E2E gira in serie (`workers: 1`) per evitare race condition sullo stato WebSocket; richiede `npm run serve` attivo prima di `npm run test:e2e`. -La copertura statement/linee della suite Vitest è oltre il 99%. Per testare `startServer()` (`server.js`) o i rami platform-dependent di `getNetworkIPs()` (`server-utils.js`) senza effetti collaterali reali (scrittura su `.segnapunti/state.json`, dipendenza dalla piattaforma), si mocka il modulo con `vi.mock`/`vi.doMock` (vedi `tests/integration/startServer.test.js` e `tests/unit/server-utils-platform.test.js`) invece di eseguire il codice reale. +Copertura statement/linee Vitest oltre il 99%. Per testare `startServer()` (`server.js`) o i rami platform-dependent di `getNetworkIPs()` (`server-utils.js`) senza effetti collaterali reali (scrittura su `.segnapunti/state.json`, dipendenza dalla piattaforma), mockare il modulo con `vi.mock`/`vi.doMock` (vedi `tests/integration/startServer.test.js`, `tests/unit/server-utils-platform.test.js`) invece di eseguire il codice reale. ## Deploy -Il progetto si distribuisce tramite Docker. L'immagine è pubblicata sul registry Gitea self-hosted (`santantonio.sytes.net`): +Docker, 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. +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. +- Nuova funzionalità: valutare come si inserisce nel flusso `action → applyAction → broadcast`, poi decidere se serve un nuovo action type o estendere uno esistente. +- Regole di gioco → solo `src/gameState.js`. Protocollo WebSocket → solo `src/websocket-handler.js`. +- Nuovo action type o regola di gioco → aggiungere test in `tests/unit/gameState.test.js`. +- Commenti e nomi di dominio in italiano (es. `servHome`, `striscia`, `nomi`); nomi tecnici standard in inglese (`state`, `action`, `handler`). +- Niente emoji in codice, UI (label, testi bottoni, banner) o commenti, salvo richiesta esplicita dell'utente.