On the task list, each task card exposed a "Visualizza Task" link that jumps
straight into an individual task. Pure operators (MeasurementTec without
Maker) should follow the guided flow and start only with AVVIA. Gate the
per-task link behind the Maker role; admins/makers who also measure keep it.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The previous change made the overlay phase-aware but it still popped up after
every task during the START run. Operators want to flow straight to the next
task. Skip showing the "Misurazioni Complete" overlay when production has not
started and this is not the last task — call goToNextTask() directly. The
overlay still appears on the last START task (for "Avvio Produzione") and in
production (for "Conferma ciclo").
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
When all subtasks of a task were measured, the completion overlay only
offered "Riepilogo" (which leaves the work cycle) or "Conferma ciclo" (which
keeps the operator stuck on the same task and starts the interval timer).
During the START run — before production is started — neither lets the
operator simply move to the next task.
Make the overlay phase-aware:
- Production started: keep "Conferma ciclo".
- START, not the last task: primary action "Task successivo" -> goToNextTask().
- START, last task: "Avvio Produzione" -> startProductionFromOverlay(), which
starts production and kicks off the interval cycle.
Add isLastTask getter and startProductionFromOverlay(); EN translation for the
new label.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two issues with the "Autorizzazione capoturno" modal:
1. Backspace and digits did not work in the username/password fields. The
numpad component installs a window-level keydown handler (for the USB
caliper / keyboard wedge) that preventDefault()s digits, Backspace, Enter
etc. It swallowed those keys inside the modal inputs. Guard handleKeydown
to ignore events whose target is an editable field (INPUT/TEXTAREA/SELECT/
contentEditable); the numpad has no text inputs of its own.
2. The modal kept the previous username/password: opening only set the flag,
and closing via backdrop click did not clear the fields. Add
openSupervisorModal()/closeSupervisorModal() that always reset username,
password and error, and use them for every open (fermo_linea,
fine_produzione, out_of_tolerance) and close (backdrop, Annulla).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Until now the out-of-tolerance authorization ("Autorizzazione capoturno")
required is_admin, conflating shift-leader authority with full system
administration. Introduce a combinable Supervisor role so a capoturno can
authorize overrides without admin privileges (roadmap D-0.8 / Phase 2).
Backend:
- users router: add "Supervisor" to the allowed roles (centralized as
VALID_ROLES, deduplicating the create/update validation).
- middleware: add require_supervisor dependency (admins still bypass).
Frontend:
- measure.validate_supervisor: authorize users with the Supervisor role OR
is_admin (was is_admin only).
- admin/users: Supervisor checkbox + orange role badge.
- profile: explicit Supervisor badge styling.
- EN translation for the new role label.
roles is a free JSON column, so no migration is required.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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>
alpine-init.js resolved the theme purely from localStorage, so it never
reflected the logged-in user's theme_pref: switching user kept the previous
user's theme because localStorage persists per-browser.
- base.html injects window.__SERVER_THEME from the session theme (empty when
anonymous, preserving OS-preference behaviour on the login page).
- alpine-init.js now treats the server value as authoritative for logged-in
users (server > localStorage > OS > light) and syncs localStorage to it.
- The navbar toggle now persists the choice via POST /auth/set-theme, which
updates the session and the user's theme_pref, so it survives navigation
and matches on the next login.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Login always redirected to measure.select_recipe, which is gated by
@role_required("MeasurementTec"). A Maker-only (or Metrologist-only) user
was therefore bounced straight to a 403 right after a successful login.
Add _post_login_landing() which selects the landing endpoint from the user's
roles (MeasurementTec -> measure, Maker -> maker recipes, Metrologist ->
statistics, admin -> users, else profile). Used for both fresh login and the
already-authenticated short-circuit.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The prod compose joined the external network 'root_default', but the
running Traefik instance is configured with --providers.docker.network=traefik
and 'root_default' is empty. Traefik therefore had no reachable address for
the tmflow-web/tmflow-api routers and served 404 for tieflow.tielogic.xyz.
Point traefik-net at the external network 'traefik'. After redeploying with
docker-compose.yml the site routes correctly (HTTPS 200, valid ACME cert,
/api/ path-prefix router working).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The reorder_tasks endpoint loaded RecipeTask with selectinload(subtasks)
only. Serializing via TaskResponse reads RecipeTask.recipe_id, a @property
that traverses the version relationship, triggering a lazy load outside the
async greenlet context -> MissingGreenlet error.
Add selectinload(RecipeTask.version), matching the pattern already used in
the get-task flow. Fixes the previously order-dependent test_reorder_tasks
failure; full suite now 179 passed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Nuovo servizio ai_service.py: estrae testo da PDF (pdfplumber) + analisi AI (OpenRouter)
- Endpoint POST /api/recipes/{id}/parse-technical-sheet con validazione file
- UI: bottone "Importa da PDF" nel task editor con modale upload + preview editabile
- Task suggeriti modificabili/rimovibili prima della creazione bulk
- Config: OPENROUTER_API_KEY e OPENROUTER_MODEL in .env
- Dipendenze: pdfplumber + httpx aggiunti a server deps
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Bottone "Avvio Produzione" appare solo dopo il primo ciclo confermato
- Conferma visiva "Produzione avviata" dopo il click
- Placeholder per futura integrazione GAIA (TODO nel codice)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Blueprint: passa measurement_interval_minutes dalla ricetta al template task_execute
- Dopo "Fine ciclo misura": countdown MM:SS con banner amber animato sopra footer
- Allo scadere: 3 beep sonori (Web Audio API 880Hz) + reset misurazioni per nuovo ciclo
- Contatore cicli visibile nel banner timer
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Recipe: nuovo campo measurement_interval_minutes (migration 003) con UI nel recipe editor
- Auto-logout: impostazione di sistema configurabile da admin/settings, timer inattività JS
con warning modale 60s prima del logout, tracking eventi utente throttled
- Navbar: aggiunto link "Impostazioni" per admin (desktop + mobile)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- setup: corregge path templates (sibling di api/, non figlio) e firma TemplateResponse
- setup: invalida sessione client dopo operazioni che ruotano l'API key (init DB, creazione admin, cambio password, attivazione utente)
- api_client: include il nome del campo nei messaggi di errore 422 (FastAPI validation)
- ui: nuovo componente _app_logo.html (SVG inline theme-aware) usato su login e navbar
- /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>
Le liste "Ricette disponibili" e "Assegnate alla stazione" passano da
una lista con separatori interni (divide-y) a card individuali con
bordo arrotondato e spaziatura: migliora la leggibilità e la
percezione di trascinabilità dei singoli elementi.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pagina /measure/execute/<task_id>:
- Il pannello destro (info subtask + tolerance + numpad) e la sidebar
marker spariscono quando il task non ha subtask: l'utente vede solo
intestazione + disegno a tutta larghezza, senza colonne vuote né
tastierina numerica fuori contesto.
- Risolta la scrollbar verticale: la pagina ora occupa esattamente
l'altezza del viewport. Il layout sfrutta nuovi block override in
base.html (body_class, wrapper_class, main_class, footer) per
disattivare scroll e footer solo su questa pagina, lasciando intatte
le altre.
Cataloghi i18n: aggiunte traduzioni EN/IT mancanti e rimossi i flag
"fuzzy" auto-generati da pybabel update (Flask-Babel ignora i fuzzy,
mostrando il msgid italiano anche in EN).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- test_recipes: split update test into in-place vs copy-on-write paths
(PUT now updates in-place when current version has no measurements).
Added _seed_measurement() helper to force copy-on-write where needed.
- test_tasks::reorder: expunge_all() between POST and GET so the shared
test session re-fetches RecipeVersion.tasks instead of returning the
cached collection. Prod uses one session per request, so identity-map
staleness only affects tests.
- test_measure::save_measurement_proxy: payload aligned to current API
(version_id is required; pass_fail/deviation are computed server-side).
All 179 tests green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Le ~42 stringhe di admin/stations.html e navbar (Stazioni, Postazione,
Gestione Stazioni, ...) non erano state estratte nel catalogo .pot dopo
l'aggiunta della GUI, quindi in EN si vedeva ancora italiano via fallback
msgid. pybabel update aveva poi auto-matchato alcune chiavi simili come
"fuzzy", producendo traduzioni assurde (Postazione → Settings, ecc.) che
Babel ignora a runtime.
Rigenerato messages.pot via pybabel extract, applicate traduzioni EN
corrette per tutte le entry fuzzy, rimossi flag fuzzy. IT lasciato con
msgstr vuoto (fallback su msgid = italiano nativo).
Tech debt: serve un test guard che faccia fail se pybabel extract produce
diff vs messages.pot committato.
GET /api/measurements required Metrologist, but the operator workflow
calls it from task_complete.html to render the post-recipe riepilogo —
silently empty for every non-Metrologist user. Open to any authenticated
user; payload carries no PII beyond numeric values + recipe ref.
Regression test added.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
STATO_PROGETTO.md
- Bumped snapshot date to 2026-04-27.
- Added "Hardening post-restructure (smoke test 2026-04-26)" section
recording the four runtime regressions surfaced by the local smoke
test (env_file path, missing Station import, UPLOAD_DIR default,
apostrophe-in-translation in Alpine), plus the recipe-assignment
modal UX rework and the new node-based JS syntax test guard.
- Added "Smoke test status" section listing the verified end-to-end
flow (MySQL up, alembic, seed, login, admin pages, MeasurementTec
workflow, hot reload).
- Bumped frontend test count 44 → 46 to reflect
test_template_js_syntax.py.
ROADMAP.md
- Added a tech-debt entry for the user-reported task_complete
riepilogo rendering anomaly (still under investigation: the curl
fetch returns the table populated, but the user reports an empty
body in the browser).
- Added a tech-debt entry for the still-pending Docker container
smoke test of the new uv-based Dockerfiles.
src/frontend/flask_app/templates/measure/task_complete.html
- Replaced sort(attribute='task_info.order_index,subtask.marker_number')
with two chained stable sorts. Jinja's sort filter does not accept a
comma-separated multi-attribute string; the previous form sorted on
a non-existent attribute and only worked by accident because the
API already returned rows in the desired order.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stations were the headline V2.0.0 feature but had no user-facing
documentation outside the architecture page. Filled the gap across
the three operational docs.
USER_GUIDE.md
- New entries in "Key Concepts": Station and Station assignment.
- New "Recipes you see are filtered by station" subsection in the
MeasurementTec workflow, explaining why the Select Recipe page may
legitimately show fewer recipes than expected and what the
"Stazione non configurata" error means at the operator level.
- New "Station Management" section under Admin Workflow covering:
the mental model, station create/edit/delete, the two-column
recipe-assignment modal, the immutable-code rule, the role of the
ST-DEFAULT seed station, and the tablet deployment cheat sheet.
- Admin role description updated to mention stations.
DEPLOYMENT.md
- Environment Variables Reference: added STATION_CODE row and noted
that an empty value triggers the deliberate fail-fast HTTP 503 on
/measure/select. Updated RATE_LIMIT_GENERAL default (300, per the
V2.0.0 perf change). Clarified UPLOAD_DIR resolves against the
project root.
API.md
- New "Stations" endpoint section listing all eight routes with
request/response examples and the 401/403/404/409 error contract:
GET / POST /stations, GET /stations/{id}, PUT /stations/{id},
DELETE /stations/{id}, GET /stations/{id}/recipes,
GET /stations/by-code/{code}/recipes (the operator-facing one used
by the Flask client), POST /stations/{id}/recipes,
DELETE /stations/{id}/recipes/{recipe_id}.
- TOC updated with the new "Stations" anchor.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
UX rework — recipe assignment modal on /admin/stations
- Replace the single "select recipe + Assegna" dropdown with a two-
column layout: Ricette disponibili (left) and Assegnate alla
stazione (right), each row with an inline action button. Top search
filter narrows both columns at once. Empty states explain *why* the
list is empty (no recipes in system, all already assigned, no match
for the filter).
- Rationale: the old dropdown silently hid every option once a recipe
was assigned, leaving the user unable to tell whether the system
was broken or simply out of unassigned recipes.
Apostrophe regressions
- /admin/stations alert/errorMsg literals reworded with double-quoted
outer JS strings ("Errore nella eliminazione" / "...assegnazione").
- /admin/users toggle confirm modal: x-text expression contained
'{{ _('… l\'utente') }}'. Inside a Jinja-rendered HTML attribute,
the apostrophe in "l'utente" closed the JS literal early, killing
the binding. Fixed by using " as the JS string delimiter so
the inner apostrophe is harmless.
Alpine x-if templates can't host nested templates
- Replaced two nested-template empty-state blocks with x-text bound
to computed getters (unassignedEmptyMessage,
assignedEmptyMessage). Alpine errored with
"Cannot set properties of null (setting '_x_dataStack')" when the
outer template's child wasn't a single root element.
Test guard widened
- src/frontend/flask_app/tests/test_template_js_syntax.py now also
parses every Alpine attribute (x-*, @*, :*) on the rendered HTML
and runs `node --check` on each expression wrapped in `void (…)`.
Previously it only inspected inline <script> bodies, which is why
the x-text bug on /admin/users slipped through. Verified the
extended test catches the original l'utente regression by reverting
+ running + restoring.
Layout regression — UPLOAD_DIR defaulted to server/uploads
- The previous .env.example shipped UPLOAD_DIR=server/uploads, which
matched the V1.x layout but pointed outside the new project tree.
Updated to UPLOAD_DIR=uploads so files land in the project-root
uploads/ volume that src/backend/config.py.upload_path resolves.
- Added uploads/general/ to .gitignore (per-user uploads, not source).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two error messages on /admin/stations rendered Italian translations
that contain an apostrophe inside a single-quoted JS literal:
alert(result.detail || '{{ _("Errore nell\'eliminazione") }}');
Jinja outputs the apostrophe verbatim, so the JS string closed
prematurely:
alert(result.detail || 'Errore nell'eliminazione');
That syntax error blew up the entire <script> block, which means
stationManagement() was never defined and EVERY Alpine binding on the
page failed silently. Symptom: clicking "Nuova Stazione" did nothing,
DevTools showed "Alpine Expression Error: openCreateModal is not
defined".
Fix: swap outer JS quotes to double quotes and reword the IT string so
the apostrophe disappears anyway ("Errore nella eliminazione",
"Errore nella assegnazione"). Same for the assignment-error path.
Regression guard: src/frontend/flask_app/tests/test_template_js_syntax.py
renders /admin/stations and /admin/users with the IT locale forced,
extracts every inline <script>, and runs `node --check` on each. The
test is skipped if `node` is not on PATH so CI without Node still
passes. Verified the test catches the original bug (revert + run +
fail) before re-applying the fix.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Both came from the src/ restructure and only show up at runtime, so
the test suite had not caught them.
- src/backend/config.py: env_file was "../../.env", which pydantic-
settings resolves against the *cwd*, not the file. Running uvicorn
or alembic from the project root therefore looked for
../../.env one level above the repo and silently fell back to the
default DB_PASSWORD ("change_me_in_production"), hiding the real
password. Now resolved as Path(__file__).resolve().parents[2] /
".env" so the lookup is always against the project root regardless
of cwd.
- src/backend/models/orm/__init__.py: Station and
StationRecipeAssignment were never imported here, so anything that
triggers Base.metadata.create_all without first importing the
setup router (which has its own Station import) ended up with no
stations / station_recipe_assignments tables. Verified locally:
/api/setup/seed used to fail with "Table tiemeasureflow.stations
doesn't exist" before this fix.
- .gitignore: ignore src/frontend/flask_app/package.json and
package-lock.json (local npm-install artifacts; the Dockerfile
installs tailwindcss directly).
Smoke verified end-to-end: uvicorn + gunicorn + MySQL, login + admin
stations + select_recipe + admin users all 200 OK.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Brings the entry-point README in line with the V2.0.0 restructure:
- Replace all server/ + client/ paths with src/backend/ +
src/frontend/flask_app/.
- Replace pip install -r requirements.txt with the uv workflow
(uv sync --extra server --extra client --extra dev).
- Manual setup section uses uv run uvicorn / uv run flask /
uv run alembic / uv run pybabel, all driven from the project root.
- Document the V2.0.0 additions: STATION_CODE per-tablet, /admin/
stations GUI, gunicorn 5x4 + uvicorn 4 worker scaling, X-Forwarded
-For-aware rate limiting (RATE_LIMIT_GENERAL default 300).
- Add tooling section (uv, pyproject.toml, uv.lock, .python-version,
pytest stack).
- Documentation section now points at the new docs/ index plus the
STATO_PROGETTO + ROADMAP architecture pair as the canonical "what
is done / what is next" references.
- Variabili d'Ambiente: add STATION_CODE, RATE_LIMIT_LOGIN,
RATE_LIMIT_GENERAL.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cleanup
- Remove src/backend/Dockerfile.legacy and
src/frontend/flask_app/Dockerfile.legacy (history is in git, build
uses the new uv-based root Dockerfile / Dockerfile.frontend).
- Remove src/frontend/flask_app/verify_i18n.py (had hardcoded paths
pointing at the old client/ tree).
Group docs/
- New docs/README.md indexes everything in one place.
- New docs/architecture/STATO_PROGETTO.md: snapshot of what works in
V2.0.0 (inherited V1.0.7 features, rev04 Phase 1 stations,
worker scaling, src/ restructure, test status, stack, decisions).
- New docs/architecture/ROADMAP.md: what's next — Phases 2-7 of the
rev04 migration with status, open client decisions (D-0.1 through
D-0.10), tech debt and time estimates for M1 / M2.
- Move PIANO_IMPLEMENTAZIONE.md (90KB V1.0.0 plan) to
docs/archive/2026-02-06-piano-implementazione-v1.md (historical).
- Move Schema sviluppo SW TieFlow_rev04-2026.docx to docs/specs/
with ISO date filename so the customer spec is now tracked.
- Move src/frontend/flask_app/I18N_SETUP.md to docs/I18N_SETUP.md
and rewrite paths to the new src/frontend/flask_app/ tree.
.dockerignore: simplified now that legacy Dockerfiles are gone;
docs/ stays excluded from the build context.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
The default 2-worker gunicorn could only serve 2 concurrent tablet requests,
queueing the rest, and the rate limiter saw every tablet as the same Nginx
container IP, so 20 users would have collectively burned through the
100 req/min general bucket.
- gunicorn: 5 workers x 4 gthread, --forwarded-allow-ips=*, access log
- uvicorn: 4 workers, --proxy-headers, --forwarded-allow-ips=*
- RateLimitMiddleware: resolve real client IP from
X-Forwarded-For -> X-Real-IP -> request.client.host
- Bump rate_limit_general 100 -> 300 req/min/IP (per tablet now)
- Flask: ProxyFix(x_for=1, x_proto=1, x_host=1) so request.remote_addr
is the tablet IP, not the Nginx IP
- APIClient: forward X-Forwarded-For + X-Real-IP to FastAPI for both
JSON and multipart/files calls; safe no-op outside request context
- 12 new tests (7 server + 5 client) covering header precedence,
forwarding behavior and ProxyFix install
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a complete browser-based interface for managing stations,
closing the last deliverable of rev04 Phase 1.
- New /admin/stations page with stations table, create/edit modal,
delete confirmation and dedicated recipe-assignment modal
- Proxy endpoints under /admin/api/stations/* covering CRUD and
recipe assign/unassign so all admin operations stay behind the
Flask CSRF + admin_required guard
- Navbar entry "Stazioni" (desktop + mobile), visible to admins only
- 10 new tests covering page render, every proxy and the non-admin
redirect
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace generic /api/recipes call with api_client.get_station_recipes(STATION_CODE).
Return 503 station_not_configured.html when STATION_CODE env var is unset.
Add station indicator to recipe selection page header.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Reads STATION_CODE from the environment and exposes it as Config.STATION_CODE
(None when unset or empty). Adds the variable to .env.example with a
per-station deployment note, and covers both read and missing-key paths with
new pytest tests.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add _seed_default_station() helper to /api/setup/seed endpoint that
creates the ST-DEFAULT station and assigns all active recipes to it,
preserving existing behaviour after migration 002 runs on live DBs.
Helper is idempotent and is called on both the normal seed path and
the early-return path (when demo data already exists).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Rinomina _RecipeSummary -> RecipeSummary: il leading underscore
segnalava "privato" ma la classe e usata come response_model pubblico
ed esposta nell'OpenAPI schema.
- Aggiunge commento esplicativo sopra /by-code/{code}/recipes sul perche
l'ordine di dichiarazione conta (protezione gia data dal tipo int di
station_id, ma esplicito per prevenire regressioni durante refactor).
- Detail message del 404 by-code uniformato a "Station not found"
(senza distinguere not-found vs inactive, evita leak di esistenza).
- Aggiunge 3 test mancanti sul router:
* test_admin_can_list_stations (copertura happy path + active_only)
* test_assign_recipe_not_found_returns_404
* test_duplicate_assignment_returns_409
Feedback da code-reviewer su Task 5. Full suite: 11/11 passed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Implements the /api/stations FastAPI router (admin-only CRUD, recipe
assignment endpoints) and the public /by-code/{code}/recipes operator
endpoint. Registers the router in main.py and adds 8 integration tests.
- Station.code: usa UniqueConstraint("code", name="uq_stations_code")
esplicito in __table_args__ invece di unique=True sulla colonna,
per allineamento con la migration 002 ed evitare drift Alembic.
- Aggiunge test test_duplicate_assignment_is_rejected per coprire
il vincolo uq_station_recipe (regola business centrale del modello).
- Sposta import IntegrityError a module-level per consistenza.
Feedback da code-reviewer su Task 2.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
TDD: test written first, confirmed failing with ModuleNotFoundError,
then model implemented; all 3 new tests pass. conftest updated to
import new models so Base.metadata.create_all picks up the tables.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- server_default='1' anziche sa.true() per compatibilita con SQLite
(usato come DB in-memory nei test)
- Rimuove Index ix_stations_code ridondante con UniqueConstraint
uq_stations_code (InnoDB crea gia un indice per i vincoli UNIQUE)
Feedback da code-reviewer su Task 1.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Le migrations Alembic sono essenziali per il deploy riproducibile:
rimuove la regola in .gitignore che le escludeva e aggiunge al
tracking la migration 001 (image_path) gia esistente ma mai committata.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Aggiunge il master plan di migrazione V1.0.7 -> V1.1.0 (rev04-2026)
con le sette fasi organizzate in milestone M1 (demo cliente) e M2
(produzione), piu il piano TDD dettagliato della Fase 1 (Stazioni e
identita per-tablet) con 17 task eseguibili.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Correct the middleware stack order (was documented backwards due to
Starlette's add_middleware wrapping behavior), document fabric-debug.js
local copy, and note full 50-900 shade palettes for Tailwind colors.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>