Commit graph

164 commits

Author SHA1 Message Date
d761a3795f fix(migrations): rend la migration ingredient_taxonomy_rework safe sur des données existantes
La version générée automatiquement par `prisma migrate diff` castait
directement chaque valeur `category` existante (ancien enum à 18
valeurs) vers le nouvel enum à 7 valeurs — échoue pour toute ligne déjà
seedée, puisqu'aucune ancienne valeur n'existe dans le nouvel enum.
En local ça passait inaperçu (reset complet sur une base vide), mais en
production (données déjà seedées) la migration échoue avec "invalid
input value for enum".

Réécrite pour ajouter les nouvelles colonnes avec une valeur par défaut
sûre (aucun cast des données existantes), puis les substituer aux
anciennes — même logique que les défauts `@default(...)` déjà
documentés dans schema.prisma : seedReferenceData() (relancée à chaque
démarrage du conteneur, voir apps/api/Dockerfile) corrige tout de suite
après la vraie catégorie/sous-catégorie de chaque ligne.

Ajoute aussi un DROP TYPE IF EXISTS défensif : une tentative précédente
de cette migration laisse le type IngredientSubcategory orphelin (son
CREATE TYPE s'exécute hors de la transaction qui échoue plus loin), une
nouvelle tentative sans ce garde-fou échouerait différemment ("type
already exists").

Vérifiée en rejouant l'historique complet des migrations sur une base
de test jetable, avec des lignes portant les anciennes valeurs d'enum
insérées à la main pour reproduire exactement l'échec signalé — la
version corrigée s'applique proprement et préserve les id existants
(donc toute vraie ligne RecipeIngredient qui y référence).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-18 12:47:14 +02:00
78e0f89503 feat(recipes): remplace les emoji par des icônes SVG épurées
Deux usages d'emoji supprimés, remplacés par un vrai jeu d'icônes SVG
(style ligne, cohérent avec layouts/nav-icons.tsx) :

- Catégories/sous-catégories du picker (auparavant préfixées d'un
  emoji dans les libellés) : nouvelles icônes CategoryIcon/
  SubcategoryIcon, une par catégorie/sous-catégorie.
- Icône par ingrédient (auparavant un emoji différent pour chacun des
  437 ingrédients) : remplacé par un petit vocabulaire de ~22
  pictogrammes génériques ("un légume", "une bouteille", "un
  fromage"...) réutilisés selon la nature de l'ingrédient plutôt
  qu'un dessin par ingrédient (irréaliste à la main pour 437 items).

- Schéma : nouvel enum IngredientIcon (22 valeurs), colonne
  Ingredient.icon passe de String? (texte libre) à IngredientIcon
  (non nullable, toujours une valeur générique pertinente désormais).
- reference-seed-data.ts : chaque groupe porte un defaultIcon (calqué
  sur sa sous-catégorie), avec override par ingrédient pour les
  exceptions (ex. les fromages dans "produits laitiers", les jus/cafés
  dans "assaisonnements").
- apps/web/src/features/recipes/ingredient-icons.tsx (nouveau) :
  22 icônes SVG + CategoryIcon/SubcategoryIcon (réutilisent le même
  vocabulaire pour représenter chaque catégorie/sous-catégorie).
- packages/shared : IngredientView.icon devient IngredientIcon (union
  de 22 valeurs) au lieu de string | null.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-18 12:30:25 +02:00
9775cbe38f feat(recipes): refonte de la catégorisation des ingrédients façon rayons de supermarché
Remplace les 18 catégories plates (mélangeant rayons génériques et
cuisines d'origine — un ingrédient pouvait finir dans "légumes" ou
"cuisine italienne" selon l'angle choisi) par une hiérarchie à 2 niveaux
sur le modèle d'un supermarché français :

- 🥦 Produits frais (légumes, fruits, herbes fraîches)
- 🥩 Boucherie & poissonnerie (viandes, volailles, poissons,
  crustacés & fruits de mer)
- 🥫 Épicerie sèche (féculents, légumineuses, graines & fruits secs,
  autres produits secs/conserves)
- 🍞 Boulangerie (pains, pâtes à cuire — 4 nouveaux ingrédients :
  pâte feuilletée/brisée/à pizza/à tarte sablée)
- 🧈 Crémerie & fromage (produits laitiers, œufs, alternatives
  végétales)
- 🧂 Condiments & épices (épices, sauces, assaisonnements)
- 🍳 Aides culinaires (bases, épaississants, sucres)

- Schéma : nouvel enum IngredientCategory (7 valeurs) + nouvel enum
  IngredientSubcategory (22 valeurs) + colonne Ingredient.subcategory.
  Migration appliquée via reset (données de référence, aucune perte
  réelle) car les anciennes valeurs d'enum n'existent plus dans les
  nouvelles.
- reference-seed-data.ts entièrement réorganisé par (catégorie, sous-
  catégorie), tous les 437 ingrédients recatégorisés un par un.
- packages/shared : INGREDIENT_CATEGORIES (7), INGREDIENT_SUBCATEGORIES
  (22) et INGREDIENT_CATEGORY_SUBCATEGORIES (mapping catégorie →
  sous-catégories, pour piloter le picker) remplacent l'ancienne liste
  plate.
- IngredientPicker : drill-down à 2 niveaux — la ligne de sous-
  catégories (couleur --color-tag, visuellement subordonnée) apparaît
  une fois une catégorie choisie, et se réinitialise au changement de
  catégorie.
- i18n : nouvelles clés recipes.form.category.* (7) et
  recipes.form.subcategory.* (22).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-18 11:47:47 +02:00
11b03b0e56 feat(recipes): toggles afficher/masquer allergènes et régimes dans le picker
Deux boutons icône en bout de la barre de recherche de l'IngredientPicker
(régimes/feuille, allergènes/triangle), même pattern bouton-icône +
état actif que FavoriteStarButton. Préférence d'affichage locale au
picker (non persistée) — n'affecte pas quels ingrédients apparaissent,
seulement si leurs badges allergènes/régimes sont visibles sur les
cartes. Nouvelle icône AllergenIcon dans nav-icons.tsx, réutilise
DietPreferencesIcon existante.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-18 11:17:55 +02:00
5890c62462 fix(recipes): overflow des préférences + régimes liés aux ingrédients
- Fix overflow horizontal sur /parametres/preferences : <fieldset> a un
  min-width: min-content par défaut du navigateur, ce qui empêchait la
  grille de l'IngredientPicker de wrapper (page entière poussée à
  ~3100px de large). Reset min-width: 0 sur .disliked-ingredients-field.
- Recatégorise les laits/crèmes végétaux (coco, amande, avoine) de
  PRODUITS_LAITIERS_OEUFS vers LIQUIDES_BOISSONS — ce ne sont pas des
  produits laitiers.
- Nouveau modèle IngredientDiet (many-to-many ingrédient <-> régime) :
  quels régimes (Végétarien, Végan, Pescétarien) chaque ingrédient
  respecte. Omnivore volontairement absent (trivial) et Sans gluten
  aussi (déjà dérivable de l'allergène Gluten existant).
- reference-seed-data.ts : chaque groupe de catégorie porte un
  defaultDiets, avec dietNames en override pour les exceptions
  (fromages, viandes, poissons, sauces à base d'œuf/poisson...).
- IngredientView.diets exposé par /reference/ingredients et /recipes,
  affiché via DietBadges dans IngredientPicker et IngredientRow.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-18 11:01:57 +02:00
ea86a4a7f1 feat(recipes): catégories d'ingrédients + nouveau sélecteur
- Prisma: enum IngredientCategory (18 valeurs) + Ingredient.category,
  migration appliquée
- reference-seed-data.ts restructuré en 18 groupes de catégories
- packages/shared: INGREDIENT_CATEGORIES + IngredientView.category
- API: category exposé par /reference/ingredients et /recipes
- Web: nouveau IngredientPicker (chips catégories + recherche + grille
  de cartes) remplaçant IngredientAutocomplete, branché dans le
  formulaire de recette et le champ aliments-pas-aimés
- i18n: clés recipes.form.category.* et libellés du picker
- Fix test reference.test.ts pour la nouvelle forme d'IngredientView

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-18 10:24:12 +02:00
acab18ac4a feat(recipes): catalogue v2 - visibilité, favoris, régimes et catalogue d'ingrédients exhaustif
Recipe catalog v2:
- Recipe gagne visibility (PERSONAL/HOUSE/PUBLIC), authorId, authorHouseId
- Favoris par utilisateur (RecipeFavorite), régimes associés (RecipeDiet)
- Aliments "pas aimés" par utilisateur (UserProfileDislikedIngredient),
  distinct des allergies médicales
- API: GET /recipes?tab=favoris|perso|foyer|publique avec contrôle d'accès
  complet, POST/DELETE /recipes/:id/favorite, édition/suppression réservées
  à l'auteur (403 NOT_RECIPE_AUTHOR), GET/PATCH /profile/disliked-ingredients
- Frontend: vue maître-détail (onglets + tableau + panneau détail),
  formulaire enrichi (visibilité, régimes), section préférences pour les
  aliments pas aimés

Catalogue d'ingrédients de référence:
- Extension du seed de 39 à ~430 ingrédients (viandes, poissons/fruits de
  mer, légumes, fruits, féculents, condiments/sauces, épices/herbes, pains
  à sandwich, cuisines italienne/asiatique/mexicaine/maghrébine, liquides
  et boissons de cuisine, bouillons/fonds)
- Chaque ingrédient lié à ses allergènes UE (IngredientAllergy) — les 14
  allergènes réglementaires restent tous couverts
- Seeding optimisé en requêtes groupées (createMany/diff ciblé) plutôt
  qu'un upsert par ligne, pour garder resetDatabase() rapide en test

Tests: 102 tests Mocha + 32 scénarios BDD, tous verts.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-18 09:29:11 +02:00
kyuno053
1367f62f32
Merge pull request #18 from kyuno053/fix/seed-reference-data-on-deploy
fix(api): seed les données de référence (régimes/allergènes) au démarrage
2026-08-18 00:06:47 +02:00
fff8c0da26 fix(api): seed reference data (diets/allergies) on container start
Found on http://batch.dev.kyuno.fr/: GET /reference/diets and
/reference/allergies both returned [] — onboarding's "régime
alimentaire" step and the profile's food-preferences tab had nothing to
show. Cause: the Docker image's CMD only ran `prisma migrate deploy`
(schema), never the seed that populates Diet/Category/Allergy.

Adds src/scripts/seed-runtime.ts — a runtime-only seed entry point
(distinct from prisma/seed.ts, the dev-time one wired to `prisma db
seed`/`prisma migrate reset` via tsx importing from ../src, which isn't
shipped in the runtime image). This one lives under src/ so tsc compiles
it into dist/ alongside everything else, and runs via plain `node`,
reusing the same idempotent seedReferenceData() (upserts by unique name)
already used by prisma/seed.ts and test-support/reset-db.ts.

Dockerfile CMD now runs it between migrate deploy and starting the
server — safe on every container start/restart, confirmed idempotent
locally (no duplicates, no error on a second run against an
already-seeded database).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-18 00:04:14 +02:00
kyuno053
77b9d14f7e
Merge pull request #17 from kyuno053/fix/cookie-secure-override
fix(api): cookie de session Secure rendu configurable (COOKIE_SECURE)
2026-08-17 23:52:13 +02:00
266f540d88 fix(api): make the session cookie's Secure flag overridable
Found on http://batch.dev.kyuno.fr/: login/signup succeeded (200/201,
profile in the body) but every subsequent request 401'd. Cause: the
session cookie is `secure: NODE_ENV === "production"`, and
docker-compose.yml sets NODE_ENV=production regardless of whether the
deployment actually has TLS in front of it. A Secure cookie is silently
never sent back by the browser over plain HTTP — no error, just a cookie
that never round-trips.

Adds COOKIE_SECURE, independent from NODE_ENV, to override the flag per
deployment. Unset (default) keeps prior behavior — secure in production.
Set COOKIE_SECURE=false only for a deployment reachable over plain HTTP
(no TLS yet), like this dev instance.

Verified locally: docker compose up with COOKIE_SECURE=false persists
and round-trips the cookie (signup -> /auth/me 200); without it, the
cookie still gets Secure as before. Full pnpm --filter api test / test:bdd
suites still pass (66 + 25 scenarios).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 23:49:05 +02:00
kyuno053
9d19136c93
Merge pull request #16 from kyuno053/fix/web-api-base-url-same-origin
fix(web): l'API n'appelait pas la bonne origine en prod
2026-08-17 23:30:54 +02:00
44e9245a0f fix(web): default API_BASE_URL to same origin, not localhost:3000
Found live on http://batch.dev.kyuno.fr/: every API call went to
http://localhost:3000 in the visitor's own browser instead of the
deployed origin, because API_BASE_URL fell back to a hardcoded
"http://localhost:3000" whenever VITE_API_URL wasn't set at build time
— which is exactly the case for the production Docker image (apps/web/.env
is excluded by .dockerignore, so Vite never sees VITE_API_URL there).

Now defaults to "" (same origin as the page), correct for the merged
single-container deployment where the API serves this very frontend
build. Native dev (pnpm dev:web) is unaffected — apps/web/.env still sets
VITE_API_URL=http://localhost:3000 explicitly there.

Verified: built the Docker image, confirmed "localhost:3000" no longer
appears in the bundled JS, and docker compose up shows /auth/me called
against the container's own origin.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 23:28:24 +02:00
kyuno053
6fec167e86
Merge pull request #15 from kyuno053/ci/single-image-and-release-pipeline
CI + Docker : image unique, pipelines séparés, release Portainer
2026-08-17 23:18:27 +02:00
c4ef989e1a ci: drop pull_request trigger, push alone covers same-repo PRs
push (all branches) already fires for a PR branch on this repo; keeping
pull_request too just ran every job twice on the same commit. GitHub
still attaches the push-triggered run to the PR by SHA regardless of
which event fired it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 23:14:46 +02:00
c2cc773c7b Fusionner web+api en une image, séparer la CI, ajouter la release
- express-tools: ExpressServer.serveStaticFrontend() sert le build du
  frontend (assets + fallback SPA), monté après les routes API et avant
  le 404 JSON. Opt-in via FRONTEND_DIST_DIR (uniquement défini dans
  l'image Docker) — le dev natif (dev:api/dev:web) est inchangé.
- apps/api/Dockerfile: build aussi apps/web, embarque son dist dans le
  runtime ; corrige au passage l'oubli de packages/date-tools. Supprime
  apps/web/Dockerfile et nginx.conf (plus de conteneur nginx séparé).
- docker-compose.yml: un seul service "app" (postgres + app), un seul
  port APP_PORT, plus de WEB_PORT/CORS_ORIGIN à coordonner entre deux
  origines. Garde `build:` (pas de registre — Portainer build depuis le
  repo Git).
- ci.yml: éclate le job unique lint-and-test+e2e en 4 jobs indépendants
  (lint/test/build/e2e), sans chaînage, déclenchés sur chaque push
  (toute branche) + PR vers main.
- release.yml (nouveau): sur tag vX.Y.Z, sanity-build de l'image Docker,
  GitHub Release avec changelog auto-généré, puis notification best-effort
  du webhook Portainer (secret PORTAINER_WEBHOOK_URL).
- README: documente le conteneur unique et le pipeline de release.
2026-08-17 23:11:26 +02:00
kyuno053
f63f9af544
Merge pull request #14 from kyuno053/fix/selectable-controls-and-deterministic-tests
Tests Planning deterministes + refonte du style radio/checkbox
2026-08-17 22:44:33 +02:00
9dd90359c9 Tests: dates fixes pour Planning + refonte des radio/checkbox
- apps/api: les tests Planning (mocha et cucumber) lisaient l'horloge
  systeme (new Date()/DateTime.utc()) pour construire leurs fixtures et
  interroger /planning, ce qui les rendait non deterministes. Ajoute
  test-support/reference-date.ts (TEST_REFERENCE_DATE, une date UTC
  fixe) et l'utilise dans planning.test.ts / planning.steps.ts a la
  place du systeme.

- apps/web: nouveau style global pour tous les radio/checkbox de
  l'app (theme-select, allergy-select, onboarding) - "carte
  selectionnable" : le controle natif reste reel/accessible mais
  visuellement cache, toute la ligne devient la surface interactive
  (bordure + fond teinte + coche au survol/selection). Corrige au
  passage le bug de fond qui causait le desalignement des radios sur
  /parametres/preferences-utilisateur (la regle generique
  input, select { width: 100% } de profile-forms.scss s'appliquait
  aussi aux checkbox/radio) et une regression de font-weight ou les
  lignes non selectionnees du theme apparaissaient en gras comme si
  elles l'etaient.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 22:36:02 +02:00
d26bae6bac Merge branch 'main' of https://github.com/kyuno053/batchCooking 2026-08-17 19:05:34 +02:00
kyuno053
c112eaa43e
Merge pull request #11 from kyuno053/feat/settings-household-account
Refonte des paramètres compte/foyer + parcours d'inscription
2026-08-17 18:53:41 +02:00
cdca6f61f4 Tests: couverture Cypress pour le repli/déploiement de la sidebar
Repli/déploiement via le bouton dédié, labels masqués (pas retirés du
DOM — restent pour le title="") mais liens toujours visibles/cliquables
icône seule, persistance via localStorage vérifiée après cy.reload().
2026-08-17 16:17:06 +02:00
73bfcd8fac Web: sidebar avec icônes + repli/déploiement
Design validé via une maquette HTML itérée avec l'utilisateur (voir
historique de conversation) avant implémentation.

- nav-icons.tsx: jeu d'icônes trait fin (24x24, inline SVG) pour les 3
  items de nav principaux, les 4 pages de Paramètres et le chevron de
  repli — pas de librairie d'icônes pour une poignée de glyphes
- AppLayout: bouton replier/déplier à côté du logo — la sidebar passe
  de 15rem à un rail 4.25rem icônes-seules ; un seul toggle de classe
  CSS sur le <aside> pilote tout (aucun sous-composant n'a besoin de
  savoir que la sidebar est repliée), état persisté en localStorage
- Logo : monogramme "bC" en repli plutôt qu'une icône arbitraire à la
  place du nom complet
- Avatar rond (initiale) dans le menu compte, popover recalée en
  largeur fixe quand la sidebar est repliée (sinon écrasée à 4.25rem)
- title="" sur chaque item pour l'infobulle native une fois replié
2026-08-17 16:16:42 +02:00
a8d897dfab Tests: couverture Cypress pour les préférences utilisateur (step 4/4)
- user-preferences.cy.ts: défaut SYSTEM, thème sauvegardé restitué et
  appliqué au document, autosave sans bouton "Enregistrer"
- sidebar.cy.ts: 4 pages de paramètres (libellé "Préférences
  alimentaires" renommé + nouvelle entrée "Préférences utilisateur")
- auth.cy.ts/onboarding.cy.ts: corrige un intercept **/planning/current
  périmé (route supprimée dans la PR précédente) — masqué jusqu'ici car
  aucune assertion n'en dépendait directement, mais provoquait un appel
  réseau non mocké
2026-08-17 15:56:36 +02:00
4bdc9c0470 Web: UserPreferencesPage (thème) + renommage "Préférences alimentaires" (step 3/4)
- ThemeContext (features/theme/) : charge/applique le thème du profil
  connecté via l'attribut data-theme (SYSTEM = pas d'attribut, laisse
  la media query prefers-color-scheme décider) ; échec réseau non
  bloquant (worst case reste au thème courant, pas d'unhandled rejection)
- UserPreferencesPage, routée /parametres/preferences-utilisateur,
  hot-save (3 boutons radio Clair/Sombre/Système)
- layout.settings.nav.preferences renommé "Préférences alimentaires"
  (évite la confusion avec ce nouveau concept plus large), nouvelle
  entrée "Préférences utilisateur"
- _theme.scss: commentaires mis à jour (l'attribut data-theme est
  désormais réellement posé, plus une simple anticipation)
2026-08-17 15:56:12 +02:00
3acde696f5 API: module preferences — GET/PATCH /preferences (step 2/4)
- getPreferences: SYSTEM par défaut si aucune ligne (même logique que
  dietId/allergies : absent = valeur par défaut, pas une omission)
- updatePreferences: upsert (crée la ligne au premier PATCH)
- Tests Mocha + Cucumber : 401, valeur invalide, défaut, création à la
  volée, cloisonnement entre profils
2026-08-17 15:55:45 +02:00
e3811dc280 Shared + migration: table user_preference (thème clair/sombre/système) (step 1/4)
- Modèle Prisma UserPreference (1-1 avec UserProfile, onDelete Cascade),
  enum ThemePreference (LIGHT/DARK/SYSTEM, défaut SYSTEM)
- packages/shared: THEME_PREFERENCES/ThemePreference, PreferencesView,
  updatePreferencesSchema
2026-08-17 15:55:18 +02:00
db427a3f30 Ignore tmp-mockups/ — maquettes HTML jetables utilisées pour la review de design 2026-08-17 14:25:13 +02:00
9d4de338e3 Tests: couverture Cypress pour la grille de planning (step 4/4)
- home-planning.cy.ts → planning-page.cy.ts : nav sidebar reprise
  telle quelle, nouveaux cas pour la grille (case vide, recettes
  placées dans les bonnes cases, colonne du jour courant, navigation
  semaine précédente/suivante, popover calendrier)
- cy.clock fige "aujourd'hui" (2026-08-17, un lundi) pour des
  assertions de date déterministes ; cy.viewport élargi (desktop-only,
  cf. décision produit) pour que les 7 colonnes tiennent sans scroll
  horizontal lors des assertions de visibilité
2026-08-17 14:19:39 +02:00
07c3851957 Web: PlanningPage — grille hebdomadaire + navigation semaine (step 3/4)
- Remplace HomePage (table jour unique) par PlanningPage : grille
  7 jours × 5 repas, groupés Matin/Midi/Après-midi/Soir (séparateurs
  pleins, plus épais entre groupes), recettes en pastilles pleine
  largeur, bouton "+" pleine largeur sans bordure (pas encore branché
  — pas de catalogue de recettes côté API, tâche future)
- WeekNavigator + CalendarPopover (sur date-tools) : flèches semaine
  précédente/suivante, popover calendrier (mois navigable, clic sur
  un jour → sa semaine), fermeture au clic extérieur
- apiClient.getPlanningForWeek(date) remplace getCurrentPlanning()
- i18n: namespace home → planning (+ nouvelles clés jours/repas/
  calendrier), common.loadError factorisé (repris par les pages
  Foyer/Préférences qui réutilisaient l'ancien home.error)
2026-08-17 14:19:16 +02:00
9b2b2c2e28 API: GET /planning?date= remplace GET /planning/current (step 2/4)
- getPlanningForDate(houseId, date: DateTime) — paramétré au lieu de
  toujours "aujourd'hui", même logique de recherche sinon
- GET /planning?date=YYYY-MM-DD, validation de forme (zod) puis de
  validité calendaire (parseDateOnly, 400 VALIDATION_ERROR sinon) —
  un seul endpoint générique au lieu de deux qui se recouvrent
- Tests Mocha + Cucumber adaptés, + cas date manquante/malformée/
  impossible et "semaine différente d'aujourd'hui"
2026-08-17 14:17:46 +02:00
b930453878 Nouveau package date-tools (Luxon) + contrat jours/repas partagé (step 1/4)
- packages/date-tools: parseDateOnly/formatDateOnly/toDateOnly,
  getWeekStart/addWeeks/buildCalendarMonth, sur Luxon DateTime (UTC)
- packages/shared: WEEK_DAYS/WeekDay, MEALS/Meal (contrat de valeurs
  documenté pour PlanningItemView.weekDay/.meal, pas encore enforcé
  en base), getPlanningByDateSchema (validation de forme de ?date=)
2026-08-17 14:17:21 +02:00
26453dd757 Tests: couverture Cypress pour les paramètres, l'onboarding et la sidebar (step 8/8)
- household.cy.ts scindé en preferences.cy.ts (régime/allergies) et
  household-settings.cy.ts (nom, créer/rejoindre/membres/suppression/
  quitter, avec et sans droits admin)
- onboarding.cy.ts réordonné (régime → foyer → allergènes), avec les
  cas skip / créer / rejoindre à l'étape foyer
- account.cy.ts: identité, suppression de compte (erreur de mot de
  passe, succès, annulation)
- sidebar.cy.ts: menu Paramètres (3 liens) et menu compte (Mon compte,
  déconnexion), redirection /foyer → /parametres/foyer
- auth.cy.ts/home-planning.cy.ts: adaptés au nouveau menu compte et au
  premier écran d'onboarding (régime, plus foyer)
2026-08-17 10:49:19 +02:00
60ca850042 Web: parcours d'inscription réordonné — régime, foyer (optionnel), allergènes (step 7/8)
- Ordre: régime (1) → foyer (2) → allergènes (3), au lieu de
  foyer → régime → allergènes
- L'étape foyer est désormais optionnelle: créer, rejoindre par code
  d'invitation, ou passer (aucun foyer n'est créé au signup)
2026-08-17 10:48:31 +02:00
7abe030bb9 Web: pages Compte/Préférences/Foyer, client API et déplacement des menus (step 6/8)
- pages/settings/AccountSettingsPage: identité + suppression de compte
  (confirmation en deux temps, mot de passe requis)
- pages/settings/PreferencesPage: régime + allergies/intolérances,
  sorti de HouseholdPage (attribut du profil, pas du foyer)
- pages/settings/HouseholdSettingsPage: sans foyer → créer/rejoindre ;
  avec foyer → renommer (hot-save), code d'invitation, membres,
  retirer un membre / supprimer le foyer (admin) ou le quitter
- HouseholdPage.tsx/.scss supprimés (contenu réparti ci-dessus)
- apiClient: createHouse/joinHouse/leaveHouse/deleteHouse/
  removeHouseMember/deleteAccount
- AuthContext: deleteAccount()
- i18n: namespaces account/preferences réorganisés, household réduit
  au foyer, common.saving/saved factorisées
2026-08-17 10:45:58 +02:00
8e9457297d Web: menu Paramètres en bas de la sidebar + menu compte utilisateur (step 5/8)
- AppLayout: retire "Foyer & profil" de la nav principale
- SettingsMenu: bloc repliable (Compte/Préférences/Foyer), ouvert par
  défaut si la route courante est sous /parametres
- AccountMenu: remplace le footer statique (salutation + déconnexion)
  par un petit menu déroulant (Mon compte, Se déconnecter)
2026-08-17 10:42:36 +02:00
3363cfad75 Tests API: couverture Mocha + Cucumber pour le foyer et la suppression de compte (step 4/8)
- house.test.ts réécrit (le foyer n'est plus auto-créé) + POST /house,
  POST /house/join, POST /house/leave, DELETE /house/current,
  DELETE /house/members/:id
- auth.test.ts: signup renvoie houseId=null, DELETE /auth/me (mauvais
  mot de passe, suppression, transfert d'admin)
- planning.test.ts/steps.ts: création explicite du foyer (POST /house)
- household.feature: scénarios créer/rejoindre/quitter/supprimer/
  retirer un membre, via un second agent (CustomWorld.secondAgent)
- auth.feature: scénarios de suppression de compte
2026-08-17 10:40:23 +02:00
7af98756dc API: le foyer n'est plus créé automatiquement à l'inscription + suppression de compte (step 3/8)
- signup() ne crée plus de House — houseId démarre à null, le foyer
  devient une étape optionnelle de l'onboarding (créer/rejoindre/passer)
- deleteAccount(): revérifie le mot de passe, transfère l'adminship ou
  supprime le foyer si nécessaire (leaveCurrentHouse), puis supprime
  le profil (cascade sur les allergies)
- DELETE /auth/me — nouvelle route, gated par mot de passe
- clearCookie n'envoie plus maxAge (corrige un warning de dépréciation
  Express, déjà latent sur /auth/logout)
2026-08-17 10:38:47 +02:00
7d5a6c05bb API: administrateur de foyer, code d'invitation, créer/rejoindre/quitter/supprimer (step 2/8)
- House.adminId / House.inviteCode (migration Prisma)
- house.service: createHouse, joinHouse, leaveCurrentHouse (transfert
  d'admin ou suppression du foyer si dernier membre), deleteHouse
  (admin-only), removeMember (admin-only)
- house.routes: POST /house, POST /house/join, POST /house/leave,
  DELETE /house/current, DELETE /house/members/:memberId
- GET/PATCH /house/current renvoient désormais membres + admin + code
2026-08-17 10:38:16 +02:00
1248461125 Shared: types/schémas/codes d'erreur pour foyer admin/invitation + suppression de compte (step 1/8)
- HouseView gagne adminId/inviteCode/members (HouseMemberView)
- createHouseSchema, joinHouseSchema
- deleteAccountSchema (nouveau packages/shared/src/schemas/account.ts)
- ErrorCode: ALREADY_HAS_HOUSE, NOT_HOUSE_ADMIN, INVITE_CODE_NOT_FOUND
2026-08-17 10:37:50 +02:00
kyuno053
97ca5f88dd
Merge pull request #10 from kyuno053/feat/profile-household-diet-allergens
Profil : création de foyer, régime alimentaire, allergies & intolérances
2026-08-17 07:55:40 +02:00
189621c306 Test: fix auth.cy.ts signup test — stale post-onboarding-wizard assertions
Retour de CI (job e2e, PR #10) : "creates a profile and lands on the
home page" échouait avec une exception non gérée ("Failed to fetch")
depuis OnboardingHouseholdPage's GET /house/current — jamais mocké
dans ce test.

Cause : ce test n'avait jamais été mis à jour quand le signup a commencé
à rediriger vers /onboarding/foyer au lieu de "/" directement (fait dans
le commit du wizard d'inscription, plus tôt sur cette PR) — seul le
texte "Bonjour Alice Martin" -> "Bonjour Alice" avait été corrigé à
l'époque, pas la destination réelle de la redirection. Invisible
localement : je n'ai pas pu faire tourner Cypress dans ce sandbox
(crash GPU headless, confirmé pré-existant), donc ce test n'avait
jamais réellement été exécuté après ce changement — seulement
typechecké.

Fix : le test vérifie maintenant qu'il atterrit sur /onboarding/foyer
(pas la home) avec le nom du foyer préaffiché, et mocke GET
/house/current comme le fait déjà onboarding.cy.ts pour ce même écran.

Vérifié via tsc --noEmit (Cypress toujours injouable localement ici) —
CI à re-checker après push.
2026-08-17 07:48:33 +02:00
8f25e53f11 Web: allergies/intolérances séparées + hot saving sur /foyer (step 8/8)
Retour fonctionnel : allergies et intolérances doivent être distinguées
dans l'UI, et /foyer doit sauvegarder à la volée plutôt que via des
boutons "Enregistrer".

- AllergySelect prend un `legend` en prop au lieu d'un libellé interne
  fixe — le même composant est rendu deux fois par chaque page
  consommatrice (HouseholdPage, OnboardingAllergensPage), une fois par
  `kind` (ALLERGY / INTOLERANCE), la sélection restant une seule liste
  d'IDs partagée.
- HouseholdPage : suppression des boutons "Enregistrer", autosave
  déclenché depuis le handler onChange de chaque champ (jamais un
  useEffect générique sur la valeur — se déclencherait aussi au
  chargement initial, sans distinction propre "chargé" vs "modifié").
  Nom du foyer et allergènes/intolérances debouncés (600ms/500ms),
  régime sauvegardé immédiatement (sélection discrète). Validation
  client (nom vide) empêche l'autosave plutôt que de déclencher un
  aller-retour API voué à l'échec.
- i18n : household.form.allergiesLabel devient "Allergies" (au lieu de
  "Allergies & intolérances"), nouvelle clé intolerancesLabel, save/
  saved remplacés par saving/saved (plus de bouton à libeller).
- Cypress (household.cy.ts réécrit, onboarding.cy.ts mis à jour) +
  specs/frontend-architecture.md + README.md.

Vérifié dans le navigateur : wizard d'inscription affiche bien les
deux groupes (12 allergies / 2 intolérances) ; /foyer sans aucun
bouton, chaque section sauvegarde automatiquement (vérifié en base
après édition du nom du foyer et du régime) ; compte de test nettoyé.

Clôt le retour fonctionnel sur la feature profil/foyer/régime/
allergènes (8 commits au total sur cette PR).
2026-08-17 00:09:09 +02:00
6cfa71730c API: séparer allergies et intolérances (kind sur Category) (step 7/8)
Retour fonctionnel : les allergies et intolérances doivent être
distinguées, pas listées ensemble.

- schema.prisma: enum AllergenKind (ALLERGY|INTOLERANCE) + Category.kind
  (@default(ALLERGY), migration écrite à la main comme précédemment —
  `migrate dev` refuse en environnement non-interactif ici — SQL généré
  via `prisma migrate diff`).
- reference-seed-data.ts: classification par substance (Gluten et
  Sulfites = INTOLERANCE, les 12 autres = ALLERGY — réaction
  non-immunitaire documentée vs réaction immunitaire classique).
  Corrige au passage l'upsert : `update: { kind }` au lieu de `update:
  {}` — un reseed doit pouvoir corriger `kind` sur une Category déjà
  existante, pas juste no-op.
- reference.service.ts / packages/shared: AllergyView gagne `kind`.
  PATCH /profile/allergies ne change pas (une seule liste d'IDs, kind
  ne sert qu'au groupement d'affichage côté client).
- Tests Mocha (29 passing) + Cucumber (15 scenarios, inchangés).

Classifié par substance (pas par utilisateur) — documenté comme
limitation connue dans le README. Web (split UI + hot saving sur
/foyer) dans le commit suivant.
2026-08-17 00:01:19 +02:00
9722f4a27b Tests + docs: onboarding/foyer Cypress coverage, specs updates (step 6/6)
- apps/web/cypress/e2e/onboarding.cy.ts — parcours complet (rempli et
  entièrement skippé) signup → 3 étapes → home, mêmes conventions
  cy.intercept que le reste.
- apps/web/cypress/e2e/household.cy.ts — /foyer : préremplissage, et
  sauvegarde indépendante de chacune des 3 sections.
- specs/frontend-architecture.md : nouvelle section "Parcours profil —
  foyer, régime, allergènes" (diagramme mermaid, les deux bugs de state
  trouvés en testant dans le navigateur), arborescence et namespaces
  i18n à jour.
- README.md : nouvelle section "Parcours profil — foyer, régime,
  allergènes", section sidebar mise à jour (Foyer & profil n'est plus
  un stub).

Cypress lui-même ne peut pas tourner en local dans ce sandbox (voir la
note existante dans le README) — vérifié via `tsc --noEmit` sur les
specs + parcours manuel complet dans le navigateur (les deux à travers
les 5 commits précédents de cette feature).

Clôt la feature profil/foyer/régime/allergènes (6 commits, cette PR) :
seed+référence -> endpoints foyer/profil -> composants partagés ->
wizard d'inscription -> page /foyer -> ce commit.
2026-08-16 23:42:54 +02:00
5d88e28810 Web: page /foyer réelle (édition foyer/profil) (step 5/6)
- HouseholdPage remplace le stub ComingSoonPage : 3 sections
  indépendamment sauvegardées (nom du foyer, régime, allergènes/
  intolérances), mêmes composants partagés que le wizard d'inscription.
  Chaque section a son propre bouton "Enregistrer" (3 ressources API
  distinctes, pas de raison qu'une modification attende les autres).
- AuthContext : ajout de refreshUser() — re-fetch GET /auth/me et met
  à jour `user`.
- Bug trouvé et corrigé en testant l'aller-retour SPA dans le
  navigateur (sidebar → Recettes → Foyer, sans rechargement complet) :
  la valeur du régime revenait à l'ancienne après sauvegarde. Cause :
  la page initialisait dietId depuis useAuth().user.dietId, un instantané
  jamais rafraîchi après une modification faite directement via
  apiClient (qui ne touche pas AuthContext). Fix : la page fetch son
  propre profil frais (apiClient.me()) au montage plutôt que de
  dépendre du contexte, et appelle refreshUser() après une sauvegarde
  réussie du régime pour que le reste de l'app reste cohérent aussi.
- household.comingSoon (clé i18n) supprimée, plus utilisée.

Vérifié dans le navigateur : préremplissage, sauvegarde par section,
persistance après rechargement ET après navigation SPA aller-retour ;
nettoyage du compte de test.
2026-08-16 23:38:48 +02:00
7bc81f51fb Web: wizard d'inscription (foyer/régime/allergènes) (step 4/6)
- SignupPage: après signup(), navigate("/onboarding/foyer") au lieu de
  "/" — la home reste inchangée, seule la destination change.
- pages/onboarding/: 3 routes top-level RequireAuth-gated (PAS nichées
  sous AppLayout — wizard plein écran sans sidebar, même langage visuel
  que /login|/signup) :
  - /onboarding/foyer — HouseNameField, préremplie avec le nom
    auto-généré du foyer (continuer sans éditer = skip implicite).
  - /onboarding/regime — DietSelect, valeur initiale depuis
    useAuth().user.dietId (pas de fetch supplémentaire nécessaire).
  - /onboarding/allergenes — AllergySelect, termine sur navigate("/").
- Bug trouvé et corrigé en testant dans le navigateur : RedirectIfAuthenticated
  redirigeait vers "/" en course avec le navigate() explicite de
  SignupPage — `user` devient non-null (via signup()) pendant que
  SignupPage est encore monté sous ce guard, qui réagit et redirige
  avant que le navigate("/onboarding/foyer") ne prenne effet. Latent
  depuis le début (invisible avant car l'ancien SignupPage naviguait
  aussi vers "/", donc les deux redirections concordaient). Fix : la
  décision de redirection est verrouillée une seule fois, au moment où
  la vérification initiale (`isLoading`) se termine, plutôt que
  réévaluée à chaque changement de `user`.

Vérifié de bout en bout dans le navigateur (inscription → 3 étapes →
home), données confirmées en base (foyer renommé, régime + 2 allergènes
enregistrés), puis nettoyage des comptes de test.
2026-08-16 23:31:59 +02:00
e512c33ffc Web: composants partagés foyer/régime/allergènes (step 3/6)
- ApiClient: getDiets/getAllergies (référence), getCurrentHouse/
  renameHouse, updateDiet, getAllergyIds/updateAllergyIds.
- features/profile/: HouseNameField, DietSelect (toujours une option
  "aucun régime" -> null, étape skippable), AllergySelect (checkboxes
  en grille + fieldset/legend, pas un <select multiple> — plus
  tapable/accessible, notamment sur mobile). Tous "dumb"/contrôlés :
  reçoivent leurs données (diets/allergies) en props plutôt que de les
  fetcher eux-mêmes — le fetch/état de chargement reste à la page
  appelante.
- profile-forms.scss partagé par les trois (même split que
  features/auth/auth-form.scss vs Login/SignupPage : styles de champs
  ici, layout de page dans chaque page consommatrice).

Pas encore utilisés (aucune page ne les importe) — le wizard
d'inscription (étape suivante) et la page /foyer les cablent.
2026-08-16 23:22:19 +02:00
1d03effc77 API: endpoints foyer/profil (nom, régime, allergènes) (step 2/6)
- GET/PATCH /house/current — renomme le foyer de l'utilisateur connecté.
  PATCH avec houseId null -> 404 HOUSE_NOT_FOUND.
- PATCH /profile/diet { dietId: number | null } — régime du profil ;
  null l'efface (étape skippable du parcours). dietId invalide ->
  404 DIET_NOT_FOUND.
- GET/PATCH /profile/allergies — allergènes/intolérances, liste d'IDs ;
  PATCH remplace l'ensemble complet (pas une fusion, cohérent avec un
  multi-select). ID invalide -> 404 ALLERGY_NOT_FOUND.
- 3 nouveaux ErrorCode (4041-4043) + libellés fr.
- Extraction de toSafeProfile() dans src/lib/safe-profile.ts —
  auparavant dupliqué dans auth.service.ts et require-auth.ts,
  profile.service.ts le réutilise aussi.
- Tests Mocha (28 passing) + Cucumber (15 scenarios) — même convention
  que le reste, doc README.

Deuxième commit de la feature profil/foyer/régime/allergènes —
composants front partagés dans le commit suivant.
2026-08-16 23:18:46 +02:00
9b7c955019 API: seed régimes/allergènes + GET /reference/diets, /reference/allergies (step 1/6)
- schema.prisma: Diet.name/Category.name deviennent @unique (pas dans le
  doc spec d'origine — ajouté pour que le seed soit idempotent par
  upsert). Migration écrite à la main + appliquée via `migrate deploy`
  (`migrate dev` refuse en environnement non-interactif ici) — SQL
  généré via `prisma migrate diff` pour matcher exactement les
  conventions Prisma.
- src/db/reference-seed-data.ts: seedReferenceData() — 5 régimes, 14
  allergènes (règlement UE 1169/2011 annexe II). Chaque allergène = une
  Category (upsert par nom) + une unique Allergy sous cette catégorie
  (Allergy elle-même ne porte pas de nom, voir schema.prisma).
  Réutilisée par prisma/seed.ts (CLI, `prisma db seed`) ET
  test-support/reset-db.ts (chaque test repart avec ces données de
  référence, pas des tables vides).
- modules/reference/: GET /reference/diets, GET /reference/allergies —
  publics (pas de requireAuth), lisibles avant qu'un compte existe
  (wizard d'inscription).
- packages/shared: DietView, AllergyView (name résolu côté serveur
  depuis Category, le split Allergy/Category reste invisible du client).
- Tests Mocha + Cucumber, doc README.

Premier commit de la feature profil/foyer/régime/allergènes (planifiée
en chat) — endpoints foyer/profil dans le commit suivant.
2026-08-16 23:09:02 +02:00
kyuno053
a14bb4155d
Merge pull request #9 from kyuno053/feat/home-planning-sidebar
Home page: sidebar layout + weekly planning
2026-08-16 22:52:33 +02:00