fc1902a1bbe416932ec8d41e24d8857e0a96baad
10 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
72f0cb1f1b |
fix(frontend): #37 punti 1,4,5,6 - 502 backend giù, barcode quotato, apici Alpine, paginazione ricette
1. api_client.py: ConnectionError/Timeout restituisce status_code=502 invece
di 0 (nessuna route confrontava == 0, verificato); i proxy che inoltrano
resp.get("status_code", 500) ereditano il fix senza altre modifiche.
4. measure.py lookup_barcode: code non stringa -> 400 (prima 500 su
code.strip()); urllib.parse.quote(code, safe="") prima di comporre l'URL
verso il backend - un barcode con '/' o '../' non spezza più il path né
raggiunge altri GET con i permessi dell'utente.
5. Ogni "'{{ _(...) }}'" dentro un attributo Alpine (x-text, :title,
:placeholder, @click) sostituito con "{{ _(...)|tojson_attr }}": la
traduzione EN di admin/stations.html:105 ("Clear the station's recipes")
ha un apostrofo che l'IT non ha, e rompeva quel binding e basta - non è
un XSS, ma stessa classe di difetto di #10. File toccati: admin/stations,
admin/users, maker/recipe_list, maker/recipe_editor, maker/task_editor,
maker/task_drawing, components/caliper_status, measure/select_recipe.
Le occorrenze dentro <script>...</script> (non attributi) sono fuori
perimetro per questo punto.
6. statistics.py dashboard(): ciclo sulle pagine (per_page=100, il massimo
del backend) invece di un'unica chiamata che troncava la tendina ricette
alla centesima.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
||
|
|
51f3fe32d4 |
fix(xss): dati query in x-data passano da tojson_attr, non interpolazione JS (#10)
select_recipe.html e recipe_list.html interpolavano recipe/lot/serial/search dentro un letterale JS con apici singoli: un valore con `'` chiudeva la stringa e iniettava chiavi nell'oggetto x-data (CSP con unsafe-eval). Anche recipe_list.html:222 (toLocaleDateString) aveva lo stesso schema su current_language. Fix: |tojson_attr ovunque. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
78816dfe8c |
fix(ui): una cornice sola, e smette di spostarsi sotto le dita
Punto 14. La segnalazione dell'operatore del 28/07 non era stata circoscritta sul codice: «le dimensioni delle viste cambiano a seconda del menu». Sono quattro meccanismi distinti, tutti leggibili senza avere il tablet in mano. Ogni vista si dichiarava la propria larghezza: sette valori diversi su diciassette template, e nessuno coincideva con quello della navbar, che sta a max-w-7xl su tutte. Il contenuto era quindi disallineato dalla barra sopra di sé, e il disallineamento cambiava a ogni pagina. Il percorso dell'operatore faceva 1280 → 1024 → tutto schermo → 1280 in quattro passaggi. Il padding verticale mescolava py-8 e py-6 fra viste consecutive, così la prima card cambiava altezza a ogni schermata. La barra di scorrimento è classica da 8px e occupa spazio: una pagina lunga la mostrava, una corta no, e la schermata di misura la toglie sempre. A ogni navigazione tutto il contenuto centrato — navbar compresa — si spostava di 8px. La schermata di misura era alta 100vh, che su Android e iOS è l'altezza con la barra dell'indirizzo nascosta: su un tablet il piede, dove stanno «Fine ciclo misura» e il tastierino, finiva sotto il bordo. Ora: .tmf-page e .tmf-page-narrow in themes.css, con i valori esatti della navbar; scrollbar-gutter: stable; 100dvh dove serve. Due larghezze in tutto il prodotto al posto di sette, e il criterio è il tipo di pagina — liste, tabelle e tele stanno larghe, i moduli stanno stretti. Login e schermata di misura tengono la loro geometria, per i motivi scritti in docs/architecture/LAYOUT.md e ripetuti in EXEMPT dentro il test: la prima non ha navbar a cui allinearsi, la seconda non deve scorrere mentre si misura. Il test è statico e guarda i sorgenti: la vista scritta domani copierà la cornice dalla vicina, ed è lì che la deriva ricomincia. Resta da chiedere all'operatore su quale schermo l'ha vista: in verticale su un tablet quasi tutte quelle larghezze collassano e il difetto non si nota. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
98b1c5ae85 |
feat(measure): l'operatore entra nella sequenza, non in un elenco
Punto 10. Sceglere una ricetta apriva la lista completa dei task e chiedeva all'operatore di decidere da dove cominciare prima ancora di aver fatto qualcosa. Ora AVVIA IN SEQUENZA porta dentro il primo task; la lista resta, un livello sotto, per quando serve vederla tutta o tornare indietro. - nuova rotta /measure/start/<ricetta>: memorizza lotto e seriale (prima lo faceva la lista, che ora si salta), verifica la tracciabilità obbligatoria e apre il primo task - GET /api/measurements/task-progress: quante quote ha già preso ogni task di misura, contate per quota e non per tentativo, delimitate dalla produzione aperta o, fuori produzione, dall'operatore - la lista distingue «Incompiuto 1/3» da «Completato 3/3»: un task lasciato a metà non somigliava più a uno mai aperto - «Visualizza singolo TASK» non è più riservato al Maker — dire «incompiuto» senza dare la strada per tornarci sarebbe una lamentela, non una funzione - nel task «Riepilogo» diventa «Completato» e si apre solo quando il ciclo è chiuso: altrimenti sarebbe la scorciatoia che rende facoltative le quote - «Fine ciclo misura» si vede da subito, spento, e dice quante quote mancano; prima compariva a task già finito, quando non serviva più saperlo Il pulsante verde del footer diventa «Task successivo»: due bottoni con la stessa parola addosso sono uno di troppo. Test: 334 (erano 309). Il side effect dei mock del client Flask ora risponde per endpoint invece che per turno — una lista posizionale si rompe appena una pagina fa una domanda in più al server, che è come crescono tutte. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
5aa3d595ad |
feat(recipes): la ricetta decide cosa pretendere e come si misura
Tre cose che prima non erano di nessuno diventano regole della ricetta, decise da chi la scrive e fatte valere dal server. Punto 8 — tracciabilita' obbligatoria. Lotto e seriale si dichiarano obbligatori sulla ricetta. L'operatore li inserisce alla selezione, dove il pulsante non si attiva finche' mancano, e la stessa regola vale sulla lista task, raggiungibile anche per link diretto, e sul barcode: uno sbarramento che dura solo finche' qualcuno prende in mano il lettore non e' uno sbarramento. La produzione non si apre e la misura non si salva senza cio' che la ricetta pretende, perche' un valore senza il suo lotto non e' riconducibile a niente, e accorgersene dopo significa accorgersene tardi. Punto 9 — inserimento manuale. Una ricetta puo' vietare i valori digitati, ed e' il valore predefinito: il calibro e' lo strumento, digitare e' cio' che va concesso. Dove e' vietato il tastierino non viene disegnato (non nascosto con i CSS: il markup nascosto e' markup che si puo' rimostrare) e restano correzione e conferma, perche' una lettura sbagliata va cancellata. Il controllo vero e' sul server: una regola che vive solo nel frontend e' un consiglio. Migliorato al passaggio il riconoscimento del calibro. Contava solo la raffica di cifre, cosi' una lettura corta come "9.5" — tre battute — finiva registrata come digitata a mano; ora conta anche l'Invio che il wedge manda dentro la stessa raffica. Senza questa correzione il divieto avrebbe respinto misure legittime. Punto 11 — formattazione delle descrizioni. A capo e grassetto sopravvivono: chi scrive le ricette incolla dal PDF della scheda tecnica e il testo arrivava appiattito, da risistemare a mano ogni volta. Nessun HTML viene accettato o salvato — il testo viene escapato e gli unici tag nel risultato sono quelli prodotti dal renderer. La sanificazione e' questa: non c'e' niente da sanificare perche' non si accetta niente. Le stesse due regole in Jinja e in JS, cosi' una descrizione si legge uguale ovunque. E la descrizione ora si vede anche in esecuzione: era scritta per l'operatore e la vedeva solo chi la scriveva. Migrazione 009: tre colonne sulla ricetta. Le due di tracciabilita' partono false, che e' il comportamento di oggi; l'inserimento manuale parte *vero* sulle ricette gia' esistenti — il default della colonna e' falso, quindi le ricette nuove sono solo-calibro, ma spegnerlo d'ufficio su quelle in uso fermerebbe una linea alla misura successiva. Chi possiede la ricetta lo decide dall'editor. Test: +26 (291). Coprono il rifiuto sul server per lotto, seriale e valore digitato, il calibro sempre ammesso, le regole che sopravvivono alla nuova versione, il tastierino assente in pagina, l'Avvia sbarrato, e il renderer delle descrizioni compreso il caso in cui si prova a farci passare un tag. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
bde8fafd77 |
feat(production): la misura torna a cercare l'operatore
Il conto alla rovescia non si ferma piu' a zero. Oltre la scadenza continua nell'altro senso e dice da quanto la linea e' in ritardo, in rosso, su tutte le schermate del percorso di misura: lista task, riepilogo, scelta ricetta. Un ritardo va letto, non dedotto. Allo scadere dell'intervallo la misura si ripropone da sola: cicalino, un avviso di cinque secondi, e l'operatore torna al primo task di misura da qualunque schermata si trovi. Un ciclo copre tutti i task di misura della ricetta, quindi solo l'ultimo lo chiude e fa ripartire l'intervallo. Farlo ripartire al primo avrebbe fatto misurare una ricetta con tre task di misura tre volte piu' spesso di come e' configurata. E' il server a decidere quale task chiude il ciclo, perche' e' lui a conoscere la sequenza: la lista dei task di misura viaggia con la produzione (measurement_task_ids), che e' anche cio' che permette a una schermata qualsiasi di sapere dove riportare l'operatore. Aggiunta la rimisura: si gira il pezzo e si misura di nuovo dentro lo stesso ciclo, senza chiudere niente e senza guadagnare tempo sulla scadenza. Le due letture restano entrambe in statistica, che e' il motivo per cui si prendono. Chi sta gia' misurando quando l'intervallo scade e' in ritardo, non perso: la banda diventa rossa e lo si lascia lavorare. Portarlo altrove a meta' ciclo cancellerebbe quote che ha davanti agli occhi senza guadagnare nulla. Sistemato anche il pulsante "Conferma ciclo" dell'overlay: alzava una bandierina locale e basta, quindi il ciclo non veniva mai registrato sul server da quella strada. Ora passa da confirmCycle come il pulsante della barra. Migrazione 008: il registro eventi della produzione impara task_measured e remeasure, e la colonna task_id — un task_measured che non dice quale task non registra niente di utile. I valori nuovi entrano tutti insieme perche' allargare una enum MySQL riscrive la tabella, stesso ragionamento dei tipi di task in 007. Verificata su SQLite (batch mode) e in MySQL con --sql. La logica dell'orologio — come si legge un ritardo, quando suona, dove sta la misura — vive in un solo posto (production-clock.js) e la schermata di misura la usa invece di riscriverla. Test: +10 (265). Coprono il ciclo che non riparte a meta', la rimisura che non sposta la scadenza, il rifiuto a linea ferma, la sequenza dei task di misura esposta dalla produzione, i proxy Flask e la validita' JS della lista task. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
a159eb2b8b |
feat(stations): ricette in lista, reset per riga e cambio stazione al volo
Punto 7 del documento modifiche del 28/07, terza richiesta di Menoncin.
GET /api/stations ora usa StationWithRecipesResponse, che era gia' definita nello
schema senza che nessun endpoint la usasse. Non costa query in piu':
Station.assignments e' gia' selectin-loaded. In lista compaiono le sole ricette
attive, coerente con cio' che l'operatore vede davvero a quella stazione.
DELETE /api/stations/{id}/recipes azzera le assegnazioni di una stazione senza
eliminarla, cosi' si riassegna da capo. E' idempotente: azzerare una stazione gia'
vuota risponde removed=0, non 404. In tabella e' un pulsante per riga, con
conferma perche' l'azione e' distruttiva.
Il cambio stazione al volo (?station=CODE, override in sessione) serve al
collaudo: senza, provare N stazioni richiede N PC. E' dietro il flag
STATION_SWITCH_ENABLED, default 0. Non e' legato al ruolo admin perche'
/measure/select richiede MeasurementTec, e gatearlo su is_admin avrebbe escluso
proprio chi fa il collaudo. In fabbrica l'identita' della stazione viene
dall'installazione locale, e misurare contro le ricette di un'altra stazione
romperebbe in silenzio la tracciabilita': per questo il codice richiesto e'
validato prima di essere memorizzato - un refuso lascia l'operatore sulla
stazione configurata con un messaggio, invece di incastrarlo su una stazione
inesistente - e quando l'override e' attivo la pagina lo dichiara, con il codice
configurato accanto e un link per ripristinare.
Le traduzioni: pybabel update aveva indovinato dieci voci da stringhe simili
marcandole fuzzy, in entrambi i cataloghi. "Azzera Stazione" era diventato "Crea
Stazione", l'opposto, su un dialogo distruttivo. Non si vedeva perche' la
compilazione salta le fuzzy e ricade sul sorgente italiano, ma sarebbe emerso al
primo build con --use-fuzzy. Tradotte per esteso le 17 stringhe nuove in IT ed EN
e tolti i flag.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
bff577b461 |
fix(measure): separate traceability inputs from recipe search
On the recipe selection page the Lotto/Seriale fields shared a single filter bar with the "Cerca ricetta" search box, so operators read them as search filters instead of data-entry fields and never filled them in. Split into two cards: a search filter, and a distinct "Tracciabilità" section with heading, helper text, accent border and monospace inputs with amber/indigo icons matching the lot/serial badges shown downstream. Add EN translations for the new strings. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
182aa9fa9a |
feat(ui): recipe thumbnail su Select Recipe + fit (object-contain) su Gestione Ricette
- /measure/select: aggiunta miniatura 64×64 accanto al nome ricetta (object-contain per mantenere l'immagine intera, non croppata). Lo schema backend RecipeSummary esponeva solo id/code/name/active: aggiunti image_path e description così la card può rendere thumb e sottotitolo. - /maker/recipes: la miniatura passa da object-cover (crop) a object-contain (fit), per visualizzare l'intera immagine della ricetta senza tagli. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
1a0431366f |
chore(v2): restructure monorepo to src/ layout with uv
Aligns the repo with the python-project-spec-design.md template chosen for V2.0.0. Big move, no logic changes. The 3 pre-existing test failures (test_recipes::test_update_recipe, test_recipes:: test_recipe_versioning, test_tasks::test_reorder_tasks, plus the client test_save_measurement_proxy) survive unchanged. Layout changes - server/ -> src/backend/ - server/middleware/ -> src/backend/api/middleware/ - server/routers/ -> src/backend/api/routers/ - server/models/ -> src/backend/models/orm/ - server/schemas/ -> src/backend/models/api/ - server/uploads/ -> uploads/ (project root, mounted volume) - server/tests/ -> src/backend/tests/ - client/ -> src/frontend/flask_app/ (Flask kept; React deroga is documented in CLAUDE.md, justified by tablet UX, USB caliper/barcode workflow and Fabric.js integration) Tooling - pyproject.toml: monorepo with [project] core deps and optional-dependencies server / client / dev. Replaces both server/requirements.txt and client/requirements.txt. - uv.lock + .python-version (3.11) committed for reproducible builds. - Dockerfile (root, backend) and Dockerfile.frontend rewritten to use uv sync --frozen --no-dev --extra server|client; legacy Dockerfiles preserved as Dockerfile.legacy for reference but excluded from build context via .dockerignore. - docker-compose.dev.yml + docker-compose.yml: build context now ".", dockerfile pointing to the root files. Code adjustments forced by the move - Every "from config|database|models|schemas|services|routers|middleware import ..." rewritten to its src.backend.* equivalent (50+ files including indented inline imports inside test bodies). - src/backend/migrations/env.py: insert project root into sys.path so alembic can resolve src.backend.* imports regardless of cwd. - src/backend/config.py: env_file ../../.env (was ../.env), upload_path resolves project root via parents[2]. - src/backend/tests/conftest.py + tests: import ... from src.backend.* instead of bare names; old per-directory pytest.ini files removed in favor of root pyproject.toml [tool.pytest.ini_options]. - .gitignore: uploads/ at root, src/frontend/flask_app/static/css/ tailwind.css path; .dockerignore tightened. - CLAUDE.md: rewrote sections "Layout del repository", "Comandi di Sviluppo", "Database & Migrations", "Test", "i18n", and all path references throughout the architecture sections. Verified - uv lock resolves 77 packages; uv sync --extra server --extra client --extra dev installs cleanly. - uv run pytest: 171 passed, 4 pre-existing failures. - uv run alembic -c src/backend/migrations/alembic.ini check loads config and metadata (errors only on the absent local MySQL). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |