The GroupBuilder sends the create form as multipart/form-data (FormData). The code
decided JSON vs form by checking — when no file was attached,
request.files was empty/falsy, so it tried get_json() on a multipart body and lost the
product field -> 400 'provide product info' even though the user filled the product name.
Fix: branch on the Content-Type (multipart/form-data -> request.form) instead of
request.files. Added a regression test (multipart create with product only -> 201).
Verified live: multipart product-only create now returns 201.
Tab 1 Admin overview (/): aggregate stats + DATE FILTER (?from&to), hardest personas,
admin quick actions (Users / + Add product).
Tab 2 My dashboard (/my/board): per-user win/lose/session summary (works for admin too).
Tab 3 Training (/training): product list w/ status + persona count; admins see all groups
(draft->analyze/edit), trainees see ready->personas; persona list shows DIFFICULTY (1-5
stars); admins get a Manage-personas button.
Settings (/settings): profile (name/email) via new PATCH /api/auth/profile + change
password + language.
Backend: analytics date filter; profile endpoint; /api/me/board + /api/chat/sessions
opened to any authed user.
Rebuilt frontend/dist.
The committed dist was stale (older build without email-resolving + username login).
Rebuild locally and commit the fresh SPA so EasyPanel serves the correct login/setup
flow. Verified fresh dist references username/must_setup/setup.
Root cause of 'wrong password' right after logout->login (no redeploy): after first-run
setup sets an email, users naturally type their EMAIL in the login field, but verify()
only looked up by USERNAME -> user not found -> 'invalid credentials' shown as wrong
password. Now verify(ident) = get_user_or_none(username) OR by_email(ident). Verified:
login by username (200) and by email (200) both work with the new password.
Tests: m0/setup/e2e all pass.
Root cause of re-login failing after first-run setup: on EasyPanel/w/o a volume, the
container's /app/backend/data (users, incl. the changed admin password) is wiped on every
recreate, so logging back in with the new password fails. Added:
- ENV DATA_DIR=/app/backend/data (explicit) + VOLUME declaration in Dockerfile
- README 'Data persistence' section: must mount a persistent volume to /app/backend/data
Verified: backend + live server login>setup>relogin-with-new-password works (200); the
failure is deployment data persistence, not login logic.
The Vue SPA is now built locally and its frontend/dist/ committed (force-added).
Dockerfile is a single-stage runtime: copy prebuilt dist + python:3.11 + Flask.
No node/npm inside the image -> deterministic, no npm/esbuild postinstall flakiness.
To update UI: edit src, npm run build, commit frontend/dist/.