docs: fix inaccuracies and add missing details to CLAUDE.md

Correct client conftest patching (4 blueprints, not 5 - admin excluded),
add middleware stack order with CORSMiddleware, fix RecipeVersionAudit
enum (add ACTIVATE), clarify server/client Docker workers (uvicorn vs
gunicorn), document tojson_attr filter, measure AJAX routes, Tailwind
custom colors, and dark mode config.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Adriano
2026-02-24 14:03:57 +01:00
parent e9a3e8e42b
commit 272685e554
+16 -12
View File
@@ -84,8 +84,8 @@ Il client Flask è un frontend server-side che comunica col backend via REST API
- **database.py**: SQLAlchemy 2.0 async engine con `AsyncSession`, pool 10+20 overflow, `pool_recycle=3600`, `expire_on_commit=False`
- **routers/**: auth, users, recipes, tasks, measurements, files, settings, statistics, reports, setup. Il task router (`tasks.py`) usa URL pattern misti: `/api/recipes/{id}/tasks` (list/create), `/api/tasks/{id}` (get/update/delete), `/api/tasks/reorder`, `/api/tasks/{id}/subtasks`, `/api/subtasks/{id}`
- **services/**: recipe_service (versioning copy-on-write), measurement_service (calcolo pass/fail), spc_service (Cp/Cpk/control chart, puro stdlib senza numpy), report_service (WeasyPrint PDF + Kaleido SVG), auth_service (bcrypt + API key)
- **middleware/**: api_key.py (auth dependency), rate_limit.py (sliding window 60s per-IP, in-memory dicts), security_headers.py (CSP con `unsafe-eval` per Plotly.js, HSTS solo con SSL), logging.py (audit trail async su DB, esclude /api/health, /docs, /openapi.json)
- **models/**: User, Recipe, RecipeVersion, RecipeTask, RecipeSubtask, Measurement, AccessLog, SystemSetting, RecipeVersionAudit
- **middleware/**: Stack order (outermost→innermost): RateLimitMiddleware → SecurityHeadersMiddleware → CORSMiddleware → AccessLogMiddleware. api_key.py (auth dependency `get_current_user()`), rate_limit.py (sliding window 60s per-IP, in-memory dicts), security_headers.py (CSP con `unsafe-eval` per Plotly.js, HSTS solo con SSL), logging.py (audit trail async su DB, esclude /api/health, /docs, /openapi.json, /redoc)
- **models/**: User (include `email`, `language_pref`, `theme_pref`), Recipe (`image_path` per preview), RecipeVersion, RecipeTask, RecipeSubtask (`image_path` per immagine specifica), Measurement (`synced_to_csv`, `input_method`), AccessLog, SystemSetting, RecipeVersionAudit
- **schemas/**: Pydantic v2 per validazione I/O API
- **migrations/**: Alembic con `alembic.ini` e `env.py` nella directory `server/migrations/`
- **tests/**: pytest + pytest-asyncio, SQLite in-memory (`sqlite+aiosqlite://`, StaticPool), WeasyPrint mockato via `sys.modules`, rate limit reset tra test
@@ -94,7 +94,7 @@ Il client Flask è un frontend server-side che comunica col backend via REST API
- **app.py**: factory pattern `create_app()`, CSRF (`WTF_CSRF_TIME_LIMIT=3600`), Babel i18n (`default_locale="it"`)
- **blueprints/**: auth (login/logout/session), maker (editor ricette con Fabric.js), measure (esecuzione misurazioni), statistics (dashboard SPC con Plotly.js), admin (gestione utenti CRUD, cambio password, toggle attivo — solo `is_admin`)
- **services/api_client.py**: singleton `APIClient` — wrapper HTTP (get/post/put/delete) con gestione errori normalizzata, timeout 30s, header X-API-Key da session
- **templates/**: Jinja2 con `{{ _('string') }}` per i18n. Context processor inietta `current_user`, `current_theme`, `current_language`, `company_logo` in tutti i template
- **templates/**: Jinja2 con `{{ _('string') }}` per i18n. Context processor inietta `current_user`, `current_theme`, `current_language`, `company_logo`, `languages` in tutti i template. Filtro custom `tojson_attr` per JSON sicuro in attributi HTML (escapa `"``&#34;`)
- **static/js/**: Alpine.js 3.x (CDN), Fabric.js 5.3.1 (CDN), locales JSON (it.json, en.json), alpine-init.js (theme store). Componenti specializzati: `numpad.js` (input numerico touch con rilevamento burst USB HID), `caliper.js` (monitor calibro USB passivo), `barcode.js` (scanner QR/barcode con camera via html5-qrcode), `csv-export.js` (export CSV con locale italiano: `;` separatore, `,` decimale), `spc-charts.js` (grafici SPC con Plotly.js), `annotation-editor.js` / `annotation-viewer.js` (editor/viewer annotazioni Fabric.js)
- **translations/**: Flask-Babel, cataloghi .po/.mo per IT/EN. Locale selector: `session["language"]` → Accept-Language → `"it"`
- **config.py**: `PERMANENT_SESSION_LIFETIME=28800` (8h), cookie secure in produzione, `BABEL_DEFAULT_TIMEZONE="Europe/Rome"`
@@ -123,6 +123,8 @@ Pattern corretto:
```
`|tojson` dentro `<script>` è sempre sicuro (nessun contesto attributo HTML).
In alternativa, usare il filtro custom `|tojson_attr` (registrato in `app.py`) che escapa `"``&#34;` per l'uso diretto in attributi HTML.
Per selettori CSS in `x-data`: usare `meta[name=csrf-token]` senza virgolette interne.
## Fabric.js Annotation Editor (`client/static/js/annotation-editor.js`)
@@ -150,7 +152,7 @@ Le ricette usano un versioning condizionale. L'endpoint `PUT /api/recipes/{id}`
La stessa logica si applica nel task router: aggiungere un task a una ricetta con measurements crea una nuova versione.
La versione corrente ha `is_current=True`, le precedenti `False`. Audit trail in `recipe_version_audit` (CREATE, UPDATE, RETIRE). Logica in `server/services/recipe_service.py`.
La versione corrente ha `is_current=True`, le precedenti `False`. Audit trail in `recipe_version_audit` (CREATE, UPDATE, ACTIVATE, RETIRE). Logica in `server/services/recipe_service.py`.
### Calcolo Pass/Fail
Ogni subtask ha 4 limiti di tolleranza: UTL (upper tolerance), UWL (upper warning), LWL (lower warning), LTL (lower tolerance) più un valore nominale. Il calcolo in `server/services/measurement_service.py`:
@@ -178,7 +180,7 @@ Flusso completo per l'operatore MeasurementTec:
3. **task_execute** (`/execute/<task_id>`) — esecuzione misurazioni con numpad touch, input USB calibro (burst detection), annotation viewer. Auto-advance al task successivo. Salvataggio AJAX via `POST /save-measurement` (proxy Flask → FastAPI)
4. **task_complete** (`/complete/<recipe_id>`) — riepilogo con tutte le misurazioni, deviazioni calcolate client-side se mancanti
Il blueprint measure include un **file proxy** (`/measure/api/files/<path>`) perché il browser non può inviare l'header `X-API-Key` direttamente al server FastAPI.
Il blueprint measure include un **file proxy** (`/measure/api/files/<path>`) perché il browser non può inviare l'header `X-API-Key` direttamente al server FastAPI. Route AJAX aggiuntive: `POST /measure/lookup-barcode` (ricerca ricetta da barcode), `POST /measure/save-traceability` (salva lotto/seriale in session).
### File Storage
Upload in `uploads/{recipe_id}/{version_id}/`. Tipi ammessi: JPEG, PNG, GIF, WebP, PDF. Limite 50MB. Thumbnail auto-generate per immagini (Pillow). Sanitizzazione filename.
@@ -195,19 +197,19 @@ Upload in `uploads/{recipe_id}/{version_id}/`. Tipi ammessi: JPEG, PNG, GIF, Web
- Client httpx: `AsyncClient` con `ASGITransport(app=app)`, override di `get_db` dependency
### Client (`client/tests/conftest.py`)
- `api_client` patchato in tutti i 5 blueprint (auth, maker, measure, statistics, admin)
- `logged_in_client` fixture pre-popola session con user dict + api_key
- `api_client` patchato in 4 blueprint (auth, maker, measure, statistics). **admin NON è patchato** — i test admin devono gestire il mock manualmente
- `logged_in_client` fixture pre-popola session con `api_key`, `user_id`, `language`, `theme` + user dict
- CSRF disabilitato nei test: `WTF_CSRF_ENABLED=False`
## Docker
Due file compose:
- **`docker-compose.dev.yml`**: sviluppo locale con Nginx reverse proxy. 4 servizi: mysql, server, client, nginx. Porta `${NGINX_PORT:-80}`.
- **`docker-compose.dev.yml`**: sviluppo locale con Nginx reverse proxy. 4 servizi: mysql, server, client, nginx. Porte `${NGINX_PORT:-80}` e `${NGINX_SSL_PORT:-443}`.
- **`docker-compose.yml`**: produzione con Traefik (SSL auto via Let's Encrypt). 3 servizi: mysql, server, client. Richiede rete esterna `root_default` (Traefik).
Container: `tmflow-mysql`, `tmflow-server`, `tmflow-client` (+`tmflow-nginx` solo dev). Network: `tmflow-net`. Volumes: `mysql_data`, `upload_data`.
Il server esegue Alembic migrations automaticamente all'avvio (`CMD` include `alembic upgrade head && uvicorn`). Il client compila Babel translations e TailwindCSS nel Dockerfile build. Entrambi usano Python 3.11-slim, Gunicorn con 2 workers.
Il server esegue Alembic migrations automaticamente all'avvio (`CMD` include `alembic upgrade head && uvicorn`). Il client compila Babel translations e TailwindCSS nel Dockerfile build. Entrambi usano Python 3.11-slim. Server: uvicorn 2 workers. Client: gunicorn 2 workers (installato inline nel Dockerfile, non in requirements.txt).
Nginx (dev): gzip abilitato, `client_max_body_size 50M`, `proxy_read_timeout 120s`, cache statica 7d, security headers duplicati a livello proxy.
@@ -235,8 +237,10 @@ Variabili d'ambiente in `.env` (copiare da `.env.example`):
- Setup: `SETUP_PASSWORD` (vuota = endpoint disabilitato)
- SSL: `SSL_CERTFILE`, `SSL_KEYFILE`
## Brand
- Colore primario: #2563EB (blu industriale)
- Colore secondario: #64748B (grigio acciaio)
## Brand & Tailwind Custom Colors
- Colore primario: #2563EB (blu industriale)`primary` in tailwind.config.js
- Colore secondario: #64748B (grigio acciaio)`steel` in tailwind.config.js
- Colori misurazioni: `measure-pass` (#059669), `measure-warning` (#D97706), `measure-fail` (#DC2626)
- Font UI: Inter
- Font numeri: JetBrains Mono
- Dark mode: `darkMode: 'class'` in tailwind.config.js, toggle via `Alpine.store('theme')` con localStorage key `tmf-theme`