14 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
109dde9c7b
|
feat(shopping-list): liste de courses agrégée depuis le planning (#73)
GET /shopping-list?date= (shopping-list.service.ts/.routes.ts) somme les ingrédients de chaque recette planifiée sur la semaine, mis à l'échelle par les portions de chaque créneau (PlanningItem.portions / Recipe.portions), regroupés par paire (ingredientId, unitId) — jamais null contrairement à GET /planning, une semaine vide redescend en items: []. Côté web, ShoppingListPage rend cette liste groupée par rayon (même IngredientCategory que IngredientPicker), triée alphabétiquement en français à l'intérieur d'un rayon (shopping-list.ts, logique pure extraite du composant). WeekNavigator (flèches + calendrier) est extrait de PlanningPage vers features/planning/ pour être partagé entre les deux pages ; ses libellés migrent de planning.* vers common.weekNav.*/ common.calendar.*/common.days.*, plus génériques pour une page qui n'est plus seulement le planning. ComingSoonPage retiré (plus aucun appelant, Liste de courses avait le dernier stub restant). Tests : Mocha (agrégation, mise à l'échelle par portions, unités non fusionnées) + Cucumber (shopping-list.feature : liste vide, groupement/tri, navigation de semaine) + mise à jour de layout.cy.ts/planning-page.cy.ts pour le nouveau rendu. Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
5d63ff9ea9
|
fix: corrige les bugs ouverts du repo (import TheMealDB, sidebar mobile) + doc (#59)
* fix(recipes): corrige plusieurs bugs d'import TheMealDB
- Les instructions TheMealDB numérotées sur leur propre ligne ("1\n\ntexte...\n\n2\n\ntexte...") créaient des étapes parasites ne contenant qu'un chiffre — filtrées désormais (#52).
- Un ingrédient compté sans mot d'unité dans le texte source (ex. "4 Egg Yolks") laissait l'import bloqué sur "Importer" indéfiniment, sans indication visuelle de la ligne en cause — matchUnit retombe maintenant sur l'unité générique "piece" quand une quantité a été extraite, et RecipeImportForm/RecipeFormPage surlignent désormais toute ligne dont l'unité manque, avec un message explicite (#53).
- Ajout de INGREDIENT_LABEL_SYNONYMS_EN pour reconnaître des formulations alternatives fréquentes chez les sources anglophones ("vanilla pod" en plus de "vanilla bean") sans élargir INGREDIENT_LABELS_EN à un tableau pour ses ~550 entrées (#54).
- Effet de bord découvert en vérifiant #53 de bout en bout : deux lignes source résolues vers le même ingrédient catalogue (ex. "Egg Yolks"/"Eggs" -> "Œuf") faisaient planter la création en 500 (contrainte unique recipe_id+ingredient_id) au lieu d'un 400 propre. createRecipeSchema rejette maintenant les ingredientId en double, et le formulaire d'import surligne les doublons avant même de soumettre.
Vérifié de bout en bout dans le navigateur (import réel de la recette "Flan" depuis TheMealDB, jusqu'au planning) en plus des tests ajoutés.
Closes #52, #53, #54
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix(layout): la sidebar réduite écrasait la barre mobile
`isCollapsed` (rail icône seule sur desktop) persiste dans localStorage
indépendamment de la largeur de fenêtre — un utilisateur ayant réduit la
sidebar sur desktop puis ouvrant la même session sur mobile (ou réduisant
la fenêtre sous 640px) gardait `.app-sidebar.collapsed` (spécificité
0,2,0 : width 4.25rem, flex-direction column), qui l'emportait sur la
règle mobile `@media (max-width: 640px)` (spécificité 0,1,0) censée passer
la sidebar en barre horizontale pleine largeur.
Le bloc `&.collapsed` est maintenant scopé sous `@media (min-width: 641px)`
— le complément exact du breakpoint mobile — donc il ne s'applique plus du
tout en dessous.
Vérifié dans le navigateur : sidebar collapsed=true dans localStorage,
viewport 375px — la sidebar calcule bien width: 375px / flex-direction:
row (barre horizontale pleine largeur) au lieu de 4.25rem/column.
Closes #27
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* docs(readme): documente GET /planning?date=, plus /planning/current
Le README documentait encore `GET /planning/current` (401 sans session,
couvre "aujourd'hui"), une route qui n'existe plus — `planning.routes.ts`
ne définit que `GET /planning?date=YYYY-MM-DD` depuis l'introduction de la
grille de semaine complète. Sans session, `/planning/current` renvoie un
404 générique (route inexistante), pas le 401 documenté.
Documente aussi POST/DELETE /planning/items au passage, absents jusqu'ici.
Closes #55
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* docs: met à jour README et specs/ avec l'état réel du code
Le code avait beaucoup évolué depuis la dernière mise à jour de la
documentation (sources externes, import de recettes, planning en
grille, pages de paramètres, thème, tests Cucumber...) sans que
README.md/specs/*.md ne suivent. Tour complet du code (backend +
frontend) et réécriture :
- specs/batch-cooking-modele.md : schéma de données réécrit depuis
schema.prisma (foyer/admin/invitation, sources, catalogue
ingrédients/unités, techniques détectées, visibilité des recettes).
- specs/backend-architecture.md : foyer, préférences/goûts, planning,
référence, sources externes (adaptateurs/registre/sync), matching
ingrédients/techniques, isolation base de test, suppression de compte.
- specs/frontend-architecture.md : routing complet, sidebar/paramètres,
thème, planning + picker, catalogue + import, composants UI partagés,
tests Cypress+Cucumber.
- specs/batch-cooking-architecture.md : module Import passe de TODO à
implémenté.
- specs/error-handling.md : liste complète des ~19 codes d'erreur.
- README.md : réécriture pour refléter tout ce qui précède, plus la
note (dangereusement obsolète) sur le partage base de test/dev — le
fix existe déjà (apps/api/.env.test), la doc décrivait encore le bug.
* feat(ingredients): ajoute jaune/blanc d'oeuf, coriandre en poudre, viandes hachées
Complète le catalogue d'ingrédients de référence (seed data) :
- jaune d'oeuf / blanc d'oeuf (dairyAndCheese/eggs, aux côtés d'"egg")
- coriandre en poudre (condimentsAndSpices/spices, aux côtés de
corianderSeeds/freshCilantro déjà présents)
- viandes hachées manquantes : veau, porc, agneau (meatAndSeafood/meats,
aux côtés de groundBeef déjà présent), dinde et poulet
(meatAndSeafood/poultry)
Libellés ajoutés dans apps/web/src/locales/fr/translation.json (source
d'affichage) et packages/shared/src/data/catalog-labels-en.ts (matching
anglais pour l'import de recettes depuis des sources comme TheMealDB).
Aucune icône ni régime dédiés : héritent des défauts de leur groupe
(EGG/SPICE/MEAT/POULTRY, mêmes dietUids que leurs groupes respectifs).
282 tests apps/api toujours au vert (resetDatabase() reseed le
catalogue à chaque test).
* fix(i18n): retire le œ ligaturé des libellés français de l'œuf
"Œuf"/"Œufs" (ingrédient, sous-catégorie, allergène) et "Jaune/Blanc
d'œuf" (ajoutés par #60) s'écrivaient avec le œ ligaturé — remplacé par
"oe" (deux lettres) partout où le mot apparaît. Ne touche pas "bœuf"
(mot différent, non concerné).
Le scénario Cucumber recipe-form.feature qui sélectionne l'ingrédient
par son libellé affiché est mis à jour en conséquence.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix(recipes): concatène les ingrédients dupliqués à l'import
Suite au retour utilisateur sur #53 (follow-up) : au lieu de bloquer
l'import et de demander à l'utilisateur de retirer une ligne en double
à la main, deux lignes source qui résolvent vers le même ingrédient
catalogue sont désormais fusionnées automatiquement, quantité
concaténée (sommée), avant même que l'écran de revue ne s'affiche.
- mergeDuplicateIngredients (recipe-translation.ts) : même unité des
deux côtés -> somme directe. Unité différente mais même UnitType
(MASS/VOLUME) -> conversion via toBaseFactor avant de sommer, exprimée
dans l'unité de la première ligne. UnitType différent, ou COUNT des
deux côtés (une "pincée" n'est pas une fraction fixe d'une "gousse",
cf. le commentaire de UnitView) -> jamais fusionnées, laissées en
double (createRecipeSchema/RecipeImportForm continuent de les
signaler, filet de sécurité déjà en place). Les lignes non résolues
(ingredientId: null) ne sont jamais fusionnées entre elles.
- rawText concaténé ("100g Sugar + 45g Sugar") pour la traçabilité.
- Branché dans previewSourceItem (sources.service.ts), juste après
translateRecipeIngredients — c'est le seul endroit où des doublons
peuvent apparaître (la création manuelle ne peut pas en produire,
IngredientPicker exclut déjà les ingrédients déjà sélectionnés).
Vérifié via l'API en local (import réel de "Flan" depuis TheMealDB) :
"100g Sugar"/"45g Sugar" -> une seule ligne Sucre, 145g.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* chore(lint): upgrade Biome vers 2.x, active noExplicitAny/noConsole/noFloatingPromises
`@biomejs/biome` passe de 1.9.4 à 2.5.9 (config migrée via `biome migrate
--write`) — nécessaire pour noFloatingPromises, une règle type-aware
apparue en 2.0 (nursery).
- noExplicitAny : déjà "recommended", actif depuis toujours, aucun changement.
- noConsole (biome.json) : bloque tout `console.*` sauf error/warn/info/
debug/table/assert — équivalent à "pas de console.log" sans interdire
les niveaux nommés (voir le nouveau log service dans le prochain commit,
qui centralise justement ces appels).
- noFloatingPromises (nursery) activé explicitement sous `rules.nursery`
sans avoir besoin d'activer le domaine "types" au sens large (ça aurait
aussi allumé des dizaines d'autres règles type-aware type
noUnresolvedImports/noUnnecessaryConditions, hors scope ici).
Le reste du diff, c'est soit du reformatage automatique (import sort, 2.x
ordonne différemment de 1.9.4 — `biome check --write --unsafe`), soit les
corrections des ~20 promesses flottantes que la nouvelle règle a fait
remonter :
- La plupart sont des `navigate(...)` non attendus (react-router v7 type
`navigate` en `void | Promise<void>`) — préfixés `void navigate(...)`,
aucun changement de comportement.
- Trois chargements initiaux en useEffect (OnboardingAllergensPage,
OnboardingDietPage, OnboardingHouseholdPage, HouseholdSettingsPage)
n'avaient jamais de `.catch()` du tout — ajouté (dégradation silencieuse
vers un état vide/par défaut, même raisonnement que le `.catch()` déjà
présent dans OnboardingSourcesPage).
- HouseholdSettingsPage : `loadHouse` était une fonction déclarée à chaque
render (donc une référence différente à chaque fois) utilisée comme
dépendance de useEffect ET passée en callback à des enfants — le
useEffect se re-déclenchait donc à chaque re-render provoqué par son
propre fetch, un vrai bug de boucle infinie de requêtes que
noFloatingPromises a fait remonter indirectement (via
useExhaustiveDependencies). Corrigé avec useCallback([]).
- RecipeDetailPanel : une clé de liste `${index}-...}` sur une liste
statique (draft.steps, sans id stable — DraftRecipeStepView n'en a pas)
— biome-ignore justifié, pas de bug réel.
- recipe.test.ts : variable `agent` non utilisée, retirée.
Vérifié : `pnpm --filter api test` (295/295), `pnpm lint` et `pnpm build`
clean sur tout le repo.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* feat(api): ajoute un log service pour les logs de fonctionnement côté serveur
Jusqu'ici, rien ne journalisait quoi que ce soit côté serveur : aucune
trace au démarrage à part un console.log ad hoc, et surtout aucune trace
des requêtes ni des erreurs gérées par ErrorHandlerService — un 500 en
production n'aurait laissé aucune trace exploitable.
- LoggerService (apps/api/src/lib/logger.service.ts) — classe (public
debug/info/warn/error, private emit), même convention que
ErrorHandlerService (packages/error-tools) : instance unique partagée
exportée (`export const logger = new LoggerService()`). Émet une ligne
JSON structurée par appel (timestamp/level/message + meta), filtrée par
seuil selon NODE_ENV (debug complet en dev, warn+ pendant les tests
pour ne pas alourdir la sortie de Mocha, info+ en production). Seul
endroit du code autorisé à toucher `console` directement (biome-ignore
justifié), toujours via une méthode nommée — jamais un console.log nu.
- requestLogger (middlewares/request-logger.ts) — une ligne par requête
terminée (méthode/chemin/statut/durée), montée en tout premier dans
app.ts, avant même setupCore (CORS/JSON/cookies), pour englober tout le
pipeline. Niveau déduit du statut (info/warn/error).
- errorLogger (middlewares/error-logger.ts) — monté juste avant
createErrorMiddleware : réutilise errorHandlerService.handle() (pur/
sans effet de bord) pour classifier l'erreur avant que la vraie réponse
ne soit construite, log en warn les 4xx routiniers (validation, 404,
401...) et en error les 5xx/exceptions non prévues (avec la stack).
- error-handler.service.ts : retire le `console.error(error)` ad hoc de
fromUnknownError — errorLogger voit désormais chaque erreur avant que
ce service ne la mappe, donc ce console.error faisait doublon (et
loggait en texte brut, pas en JSON structuré).
- server.ts : le console.log de démarrage passe par logger.info.
Vérifié : pnpm --filter api test (303/303, dont 8 nouveaux tests sur
LoggerService), pnpm lint/build clean, testé en live (pnpm dev:api +
curl) — logs JSON corrects pour un 200, un 404, un 401.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* style: préfixe tous les membres private/protected par _
Convention demandée par l'utilisateur : `emit` -> `_emit`, sur toutes les
classes du repo, pas seulement le nouveau code. `public` reste sans
préfixe.
- LoggerService (apps/api) : _minSeverity, _emit.
- ApiClient (apps/web) : _request (39 sites d'appel mis à jour).
- ErrorHandlerService (packages/error-tools) : _fromZodError,
_fromHttpError, _fromUnknownError.
- ExpressServer (packages/express-tools) : _app, _registeredRoutes.
Aucun changement de comportement — pur renommage interne, aucune méthode
private/protected n'était appelée depuis l'extérieur de sa classe.
Vérifié : pnpm --filter api test (303/303), pnpm lint/build clean sur
tout le repo (apps/api, apps/web, packages/*).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* docs(specs): documente les conventions de développement du repo
Nouveau specs/dev-conventions.md — jusqu'ici ces règles n'existaient que
dans l'historique de commits/PR (classes vs objets littéraux pour la
logique de service, préfixe _ sur private/protected, règles Biome
actives, log service, tests sans mocks de la DB, conventions git/PR...),
rien de centralisé pour un futur contributeur (humain ou Claude Code).
Référencé depuis README.md, section "Qualité / Tests".
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* refactor(web): regroupe pages/ par section au lieu d'un dossier à plat
pages/ mélangeait 8 fichiers directement à sa racine (LoginPage,
SignupPage, PlanningPage+scss, RecipesPage, RecipeFormPage,
ImportRecipePage, ShoppingListPage, ComingSoonPage+scss) à côté de deux
sous-dossiers déjà groupés (onboarding/, settings/) — incohérent, et
difficile à parcourir une fois le nombre de pages monté. Un sous-dossier
par section routée, même règle que onboarding/settings existants :
- pages/auth/ — LoginPage, SignupPage
- pages/planning/ — PlanningPage + planning-page.scss
- pages/recipes/ — RecipesPage, RecipeFormPage, ImportRecipePage
- pages/shopping-list/ — ShoppingListPage
ComingSoonPage (+ .scss) déménage vers components/ui/ — ce n'est pas une
page routée elle-même (ShoppingListPage l'enveloppe), c'est un composant
UI générique réutilisable, sa place est aux côtés de Dialog/Tooltip/etc.,
pas dans pages/.
Chemins relatifs internes de chaque fichier déplacé mis à jour (un niveau
de profondeur en plus), imports dans App.tsx repointés, tri Biome
réappliqué. specs/frontend-architecture.md mis à jour (arborescence +
références de chemin).
Vérifié : pnpm build clean (apps/web, 1952 modules), pnpm lint clean sur
tout le repo, testé en live dans le navigateur (login/signup, planning,
recettes, nouvelle recette, liste de courses, paramètres) — aucune route
cassée.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* refactor(web): regroupe features/recipes/ par sous-domaine au lieu d'un dossier à plat
20 fichiers à plat -> badges/ (DietTagSelect, DietBadges, AllergenBadges,
ReproducibleBadge, FavoriteStarButton), ingredients/ (IngredientPicker,
IngredientRow, ingredient-icons), steps/ (StepListEditor, StepDescription,
highlight-tech-steps), sources/ (RecipeSourcesPanel, SourceItemTable,
RecipeImportForm, recipe-import-draft, useEnabledSources).
RecipeTable/RecipeTabs/RecipeDetailPanel et recipes.scss restent à la
racine (composants transverses aux sous-dossiers, partagés par plusieurs
d'entre eux). Chemins relatifs corrigés dans les fichiers déplacés et chez
tous leurs importeurs externes (pages/recipes/*, features/planning/
RecipePickerDialog.tsx, features/profile/DislikedIngredientsField.tsx),
doc mise à jour (specs/frontend-architecture.md, specs/batch-cooking-
modele.md).
Vérifié : tsc --noEmit, biome check, build complet, 303 tests API,
vérification live navigateur (planning, /recettes, /recettes/nouvelle).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* refactor(api): regroupe lib/ par sous-domaine au lieu d'un dossier à plat
9 fichiers à plat -> recipe-sources/ (recipe-source-adapter, recipe-source-
errors, recipe-source-registry) et recipe-matching/ (recipe-translation,
ingredient-matcher, tech-step-matcher). jwt.ts, safe-profile.ts et
logger.service.ts restent à la racine de lib/ (pas de sous-domaine
partagé avec les autres).
Chemins relatifs corrigés dans les fichiers déplacés (profondeur +1 vers
db/) et chez tous leurs importeurs (modules/sources, modules/recipe,
sources/*, db/recipe-source-sync.ts, 12 fichiers de test), doc mise à
jour (specs/backend-architecture.md, specs/batch-cooking-architecture.md).
Vérifié : tsc --noEmit, biome check, build complet, 303 tests API.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* refactor(api): regroupe test/ par sous-domaine, miroir de src/lib/
18 fichiers à plat -> recipe-matching/ (ingredient-matcher, recipe-
translation, tech-step-matcher — miroir de lib/recipe-matching/),
recipe-sources/ (json-ld-recipe, recipe-source, recipe-source-sync,
the-meal-db — miroir de lib/recipe-sources/), sources/ (sources,
sources-index — module + registration src/sources/index.ts).
Les tests par domaine API sans regroupement naturel (auth, health,
house, logger.service, planning, preferences, profile, recipe,
reference) restent à la racine de test/, un fichier par domaine — même
logique que jwt.ts/safe-profile.ts restés à la racine de lib/.
Chemins relatifs corrigés (../src/ -> ../../src/, ../test-support/ ->
../../test-support/ dans les fichiers déplacés qui appellent
resetDatabase). .mocharc.json ("test/**/*.test.ts") couvre déjà les
sous-dossiers, aucun changement de config nécessaire.
Vérifié : biome check, 303 tests API.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* feat(convention): impose try/catch autour de chaque await/corps async
Nouvelle règle de dev : aucun await nu, et un corps de fonction/méthode
async doit intégralement vivre dans un try/catch (pas seulement la ou
les lignes qui awaitent). Documentée dans specs/dev-conventions.md avec
son périmètre (code applicatif — services/hooks/composants/middlewares
— routes *.routes.ts exemptées car déjà couvertes par
wrapAsyncHandler ; tests et scripts one-off exemptés aussi).
Appliqué rétroactivement à tout le code applicatif qui ne l'était pas
déjà :
- api : auth/house/profile/preferences/planning/reference/recipe/
sources .service.ts, recipe-source-sync.ts, recipe-translation.ts,
ingredient-matcher.ts, tech-step-matcher.ts, json-ld-recipe.ts,
the-meal-db.ts — un try/catch par fonction async, rethrow simple
(le middleware d'erreur logge déjà tout centralement, voir
error-logger.ts) sauf quand un catch avait déjà une logique propre
(ex. le retry de createHouse).
- web : api/client.ts (_request), AuthContext.tsx, ThemeContext.tsx,
AppLayout.tsx (handleLogout), HouseholdSettingsPage.tsx (handleCopy/
handleRemove/handleDelete/handleLeave) — la plupart des handlers de
formulaire avaient déjà ce pattern, seuls ceux qui laissaient un
await nu ont été corrigés.
lint/complexity/noUselessCatch désactivé dans biome.json (interdisait
justement le catch-qui-rethrow que cette convention impose).
Vérifié : tsc --noEmit (api+web), biome check (0 erreur, repo entier),
build complet, 303 tests API, vérification live navigateur (thème,
déconnexion, copie du code d'invitation).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix(web): corrige l'import cassé de highlight-tech-steps.cy.tsx
Oubli lors du regroupement de features/recipes/ par sous-domaine
(refactor(web): regroupe features/recipes/...) : le déplacement de
highlight-tech-steps.ts vers features/recipes/steps/ n'avait pas été
répercuté dans ce test composant Cypress (hors de apps/web/src, donc
raté par la recherche de référence externe à l'époque) — faisait
planter le job e2e en CI ("Failed to fetch dynamically imported
module").
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
|
||
| 85fd9bae7d |
feat(planning): le picker prévisualise avant de confirmer, formulaire de revue intégré
Trois ajustements successifs sur le dialogue de sélection de recette (RecipePickerDialog), demandés en continu après le premier correctif de débordement : 1. Dialogue élargi et à hauteur fixe (95vw plafonné à 85rem, 80vh) au lieu de dépendre du contenu, avec la répartition liste/détail redéfinie en fractions du dialogue lui-même (3fr/2fr) plutôt qu'en vw — cette dernière suivait la largeur du viewport, sans rapport avec la largeur désormais fixe du dialogue. 2. Le formulaire de revue d'import (ex-ImportRecipePage) est extrait dans un composant partagé, RecipeImportForm — toujours monté en page autonome (route directe/rechargement), mais désormais aussi intégré comme une étape du dialogue lui-même quand un item de source a besoin d'une résolution manuelle, au lieu de naviguer et perdre le contexte du picker (recherche, filtres, créneau). 3. Cliquer sur une recette dans le dialogue ne fait plus que la sélectionner/prévisualiser (RecipeDetailPanel, comme /recettes) — plus de saut automatique vers l'étape suivante. Un nouveau pied de dialogue (Dialog.tsx gagne une prop ) porte Confirmer/ Fermer : Confirmer agit sur la sélection en cours (recette réelle → étape portions existante ; item de source pas encore importé → import transparent ou formulaire intégré, point 2). Les onglets réguliers gagnent leur propre paire maître-détail (RecipeTable + RecipeDetailPanel, showActions=false) sur ce même modèle ; les onglets source prévisualisent désormais aussi les items déjà importés en interne (RecipeSourcesPanel), plus de saut direct. Cypress (planning.feature/planning.ts) mis à jour en conséquence : sélectionner puis confirmer sont deux étapes distinctes, le clic sur la ligne ne déclenche plus rien tout seul. Bug pré-existant trouvé en testant en direct (sans rapport avec ce qui précède) : l'import d'une recette source plante avec une contrainte d'unicité Prisma dès que deux lignes d'ingrédient se résolvent au même ingrédient catalogue — signalé séparément (tâche en arrière-plan), pas corrigé ici. Vérifié en direct (navigateur, comptes de test) : sélection sans saut d'écran, pied de dialogue activé/désactivé correctement, Confirmer sur un item de source non résolu bascule vers le formulaire intégré, Fermer ferme bien le dialogue. pnpm exec tsc -b --force (web) — propre. pnpm exec biome check — propre. pnpm --filter web build — propre. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
|||
| 260bc3dc05 |
style(planning): dialogue de sélection encore un peu plus large
92vw/75rem -> 95vw/85rem — vérifié en direct, dialogue passe de 1178px à 1216px sur un viewport 1280px. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
|||
| b0c1d46acc |
style(planning): répartit la largeur du dialogue par rapport à lui-même
.recipes-page__catalog dimensionne son panneau de détail avec minmax(35vw, 38vw) — une fraction du *viewport*, cohérent sur /recettes (cette grille occupe quasiment toute la page) mais sans rapport avec ce dialogue, désormais de largeur fixe (92vw plafonné à 75rem, voir commit précédent) : à un viewport large, 38vw dépasse largement la largeur réelle du dialogue et déséquilibre la répartition liste/détail. Remplacé, sous .recipe-picker-dialog uniquement, par un ratio 3fr/2fr relatif au dialogue lui-même — la répartition (60/40) reste identique quelle que soit la largeur de l'écran, vérifié en direct à 1280px et 1600px de large (ratio mesuré 1.50 dans les deux cas). pnpm --filter web build — propre. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
|||
| 933882a527 |
style(planning): dialogue de sélection plus large, hauteur fixe 80vh
Suite au correctif précédent (débordement du catalogue) : le dialogue lui- même passe de 56rem/hauteur-au-contenu à 92vw plafonné à 75rem, avec une hauteur fixe de 80vh (au lieu du simple plafond hérité de Dialog.tsx) — la zone de navigation garde désormais une taille généreuse et constante quel que soit l'onglet/la quantité de contenu, plutôt que de dépendre de ce qui tient à l'écran. Vérifié en direct (navigateur, 1280×800) : dialogue 1178×640px (92vw × 80vh), catalogue/tableau/détail correctement bornés à l'intérieur. pnpm --filter web build — propre. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
|||
| fc8f38afde |
fix(planning): corrige le débordement du dialogue de sélection pour les sources
Le dialogue de sélection de recette (RecipePickerDialog) affichait bien les données renvoyées par le serveur, mais rien ne s'affichait dans la fenêtre tant que le nombre d'items dépassait sa hauteur — reproduit en direct via le compte de test : la tab TheMealDB (25 items, contrairement aux quelques recettes perso habituelles) rend le bug visible alors qu'il touchait déjà le tableau simple des autres onglets, juste sans jamais assez de lignes pour le déclencher. Cause : .recipes-page__catalog et .recipe-table-wrap se dimensionnent via flex: 1; min-height: 0, ce qui ne fonctionne que si leur parent est display: flex — vrai sur /recettes (.recipes-page l'établit), faux dans ce dialogue où le contenu est monté directement dans .dialog-panel__body (Dialog.tsx), un simple conteneur en flux bloc. Sans ce contexte, la zone catalogue grossissait à la taille de son contenu (1499px mesuré en direct contre 605px de hauteur réellement disponible) au lieu d'être bornée et scrollable dans ses propres limites. Correctif scoping .dialog-panel__body en display: flex; flex-direction: column uniquement sous .recipe-picker-dialog (tous les autres appelants de Dialog gardent le flux bloc par défaut), plus flex: 1; min-height: 0 sur .recipe-table-wrap pour qu'il profite du même contexte. Vérifié en direct (navigateur, compte de test, tab TheMealDB) : la zone catalogue passe de 1499px à 450px (borné), les lignes redeviennent visibles/cliquables sans affecter les autres onglets ni l'étape de confirmation des portions (rendue par un Dialog séparé, sans la classe recipe-picker-dialog). Tenu aussi en viewport réduit (1024x640). pnpm exec tsc -b --force (web) — propre. pnpm --filter web build — propre. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
|||
| 73ae8169a1 |
fix(recipes): retire l'import manuel, le planning importe seul
Correction de comportement sur la gestion des recettes de sources externes — l'implémentation précédente avait dérivé d'une lecture erronée du besoin : - Plus aucun bouton d'import nulle part. Parcourir une source (RecipesPage, hors planning) ne fait plus jamais que prévisualiser — RecipeDetailPanel n'affiche plus de lien "Importer cette recette", seulement un bouton icône discret vers la page d'origine quand la recette en a une (nouveau .recipe-detail-panel__source-link, même emplacement que l'étoile favori). - Une recette externe n'est importée dans la base qu'au moment où quelqu'un l'ajoute effectivement à son planning — jamais avant. RecipePickerDialog.handleSelectDraftItem est désormais le seul endroit de toute l'appli qui importe quoi que ce soit : cliquer sur un item pas encore importé y déclenche une tentative d'import transparente (POST /sources/.../import puis POST /planning/items), sans écran intermédiaire, dès que rien ne manque (tryBuildCompleteImport, nouveau apps/web/src/features/recipes/ recipe-import-draft.ts). Seul un ingrédient non résolu (ou une erreur réseau) fait encore basculer vers l'écran de revue existant (ImportRecipePage), pré-rempli, pour compléter ce qui manque. - RecipeSourcesPanel gagne onSelectDraftItem (remplace planningSlot, qui n'a plus de raison d'être puisqu'il n'y a plus de lien d'import à qui le transmettre) : quand ce callback est fourni (RecipePickerDialog uniquement), un item pas encore importé n'est plus prévisualisé sur place, il est remonté tel quel à l'appelant. Tests : - planning.feature : le scénario existant retire l'étape "je clique le lien Importer cette recette" (redirection désormais automatique puisque le draft de test a un ingrédient non résolu) ; nouveau scénario pour le chemin transparent (draft entièrement résolu, aucun écran de revue). - recipe-sources.feature : le scénario qui important depuis /recettes (hors planning) est supprimé — cette capacité n'existe plus hors planning. Le scénario de deep-link vérifie maintenant l'absence du bouton d'import et la présence du lien discret. - pnpm exec tsc -b --force (web) — propre. - pnpm exec biome check — propre. - pnpm --filter web build — propre. - Cypress non exécutable localement sur cette machine (crash GPU Electron connu) — scénarios vérifiés par relecture attentive contre le markup/les clés i18n réels ; CI (GitHub Actions) fera foi à l'exécution. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
|||
| 4381e63045 |
feat(recipes): chaque source activée devient sa propre tab
Nouvelle correction demandée sur cette PR : l'onglet générique
« Sources » (avec un <select> interne quand le foyer en a activé
plusieurs) devient une tab à part entière par source activée — au même
niveau que Favoris/Perso/Foyer/Publique, plus transparent qu'un
sélecteur caché dans un sous-menu.
- `RecipeTabs` accepte désormais `sources: SourceView[]` et rend une
tab par source (icône propre à la source si elle en a une, sinon
l'icône générique `SourcesIcon` ; libellé = le nom réel de la
source, pas une clé i18n). Nouveau type `RecipesPageTab` en
`RecipeTab | "source:<key>"`, avec `sourceTabValue`/
`parseSourceTabValue`/`isSourceTab` comme seul point d'assemblage/
lecture de ce format.
- Nouveau hook partagé `useEnabledSources` (déplacé hors de
`RecipeSourcesPanel`, maintenant utilisé par `RecipesPage` ET
`RecipePickerDialog` pour construire leurs tabs).
- `RecipeSourcesPanel` simplifié : `sourceKey` devient une prop requise
(fournie par la tab elle-même) au lieu d'un état interne avec son
propre sélecteur — plus de `<select>`, plus de message « aucune
source activée » (une tab qui n'existe pas ne peut plus être
cliquée). Remonté via `key={sourceKey}` par l'appelant au changement
de tab, même convention que `RecipePickerDialog`/`CalendarPopover`
ailleurs dans l'app.
- Un bug distinct trouvé en écrivant ce changement : passer tel quel
`initialSelection` (dérivé de l'URL) au panneau nouvellement monté
en changeant directement de tab source à tab source aurait fait
prévisualiser l'ancien item contre la nouvelle source. Gardé en ne
transmettant `initialSelection` que lorsqu'il appartient réellement
à `activeSourceKey`.
Aucun changement backend.
Tests :
- Vérifié manuellement en local (foyer avec TheMealDB activé) :
tab dédiée dans /recettes et dans le sélecteur du planning, parcours
d'un item, aperçu unifié, aucune régression console.
- Cypress : `recipe-sources.feature`/`planning.feature` mis à jour
(« I click the button "Sources" » → « ... "TheMealDB" »), scénario
« aucune source activée » réécrit pour vérifier l'absence de tab
plutôt qu'un message dans un onglet qui n'existe plus.
- `pnpm exec tsc -b --force` (web) — propre.
- `pnpm exec biome check` — propre.
- `pnpm --filter web build` — propre.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
|||
| 991f91bc0e |
feat(planning): ajouter au planning déclenche l'import si besoin (étape 4/4)
Dernière étape du plan « onglet Sources » : le sélecteur de recette du planning (`RecipePickerDialog`) gagne l'onglet « Sources », jusqu'ici volontairement exclu faute d'écran de revue à qui transmettre un item choisi (voir étape 3, #47). - Sélectionner un item déjà importé se comporte exactement comme choisir cette même recette depuis un onglet normal (résolue via `GET /recipes/:id`, direction vers l'étape « combien de portions ? » du dialogue, sans navigation). - Sélectionner un item pas encore importé bascule vers l'écran de revue existant (`ImportRecipePage`), avec le créneau du planning porté par la query string (`?planningDate=&planningWeekDay=&planningMeal=`). Un import réussi y ajoute alors automatiquement la recette fraîchement créée à ce créneau (`POST /planning/items`, avec les portions du formulaire) avant de revenir sur le planning — plutôt que d'atterrir sur la page de la recette comme le fait un import « classique ». - `RecipeSourcesPanel`/`SourceItemPreviewPanel` généralisés en conséquence : la première ne navigue plus elle-même vers la recette déjà importée (`onSelectImportedRecipe` renvoie l'id, chaque appelant décide), la seconde propage le créneau optionnel sur son lien d'import. Aucun changement backend : `POST /sources/:sourceKey/import/:externalId` et `POST /planning/items` existaient déjà et suffisent tels quels — une fois la recette importée, `GET /sources/:sourceKey/browse` la marque déjà `alreadyImported` automatiquement (logique déjà couverte par `sources.test.ts`). 282 tests API toujours au vert, aucune régression. Tests : - Cypress : nouveau scénario Gherkin bout-en-bout (`cypress/e2e/planning.feature`/`planning.ts`) — ouvrir le sélecteur depuis un créneau vide, parcourir Sources, importer un item non résolu (ingrédient à compléter compris), vérifier que la requête d'ajout au planning porte bien le bon créneau/les bonnes portions, que la recette apparaît dans la bonne case de la grille après le retour sur "/", puis que rebrowser la source la marque désormais comme déjà importée. Suite : plan « onglet Sources » terminé (étapes 1 à 4). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
|||
| b5a12cf489 |
feat(recipes): onglet Sources — parcourir les recettes externes (étape 2/4)
Deuxième étape du chantier "onglet Sources" : l'UI de parcours, construite contre les endpoints backend de l'étape 1 (#45). L'onglet désactivé placeholder de RecipeTabs devient un vrai onglet fonctionnel. - RecipeTabs.tsx : nouveau type RecipesPageTab (RecipeTab | "sources") — gardé hors du type partagé RecipeTab puisque l'API n'a pas de tab=sources à valider. Un prop `tabs` optionnel restreint quels onglets s'affichent — RecipePickerDialog (choix d'une recette pour un planning) s'y restreint aux 4 onglets réels, parcourir des sources externes en plein milieu de ce dialogue n'a pas de sens sans le flux de revue/import. - Nouveau RecipeSourcesPanel.tsx : contenu de l'onglet "Sources" — autonome (son propre master-detail), ne partage pas le fetching RecipeTab de RecipesPage puisqu'il parcourt le catalogue *live* d'une source (GET /sources/:key/browse), pas la table Recipe sauvegardée. Sélecteur de source si le foyer en a activé plusieurs ; sélectionner un item déjà importé navigue directement vers la vraie recette (SourceItemTable + navigate), un item pas encore importé affiche un aperçu en lecture seule (SourceItemPreviewPanel, réutilise StepDescription — les tech steps sont donc déjà surlignés dans l'aperçu). - Bug trouvé et corrigé en écrivant le scénario Cypress : cliquer un item déjà importé changeait l'URL mais restait affiché sur l'onglet Sources (RecipesPage ne rend RecipeDetailPanel/RecipeTable qu'en dehors de l'onglet "sources"). RecipeSourcesPanel prend maintenant un callback `onViewImportedRecipe` pour repasser sur un onglet réel avant de naviguer. Tests : nouveau recipe-sources.feature (parcours utilisateur complet — onglet vide, parcours avec items importés/non importés, aperçu avec surlignage de technique) ; recipes.cy.ts corrigé (assertion obsolète sur l'ancien placeholder désactivé). Étape suivante (3/4) : écran de revue (corriger les ingrédients non résolus) + finalisation de l'import. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
|||
| 0cb1dccd92 |
feat(recipes): ajouter le nombre de portions couvertes par une recette
Ajoute Recipe.portions (combien de portions la recette produit telle qu'écrite) — formulaire de création/édition, fiche détail, migration Prisma (backfill à 4, même pattern que planning_item.portions). Le sélecteur de recette du planning pré-remplit désormais son propre champ "portions" depuis cette valeur au lieu de toujours démarrer à 1 (RecipeSummaryView.portions), tout en gardant PlanningItem.portions indépendant (une recette peut être mise à l'échelle pour un créneau). Couverture : tests API (création/édition/validation), scénarios cypress (formulaire + préchargement en édition). |
|||
| c29648e293 |
fix(ci): corrige lint/tests cassés par la migration portions
- Dialog.tsx : remplace le div role="dialog" par un <dialog> natif (showModal) — corrige lint/a11y/useSemanticElements, récupère gratuitement le piège de focus et l'Échap natifs. Le clic extérieur est rebranché en imperative addEventListener pour éviter lint/a11y/useKeyWithClickEvents sur un élément non interactif. - RecipePickerDialog.tsx : retire l'autoFocus (lint/a11y/noAutofocus), ordre des imports/formatage corrigés par `biome check --write`. - apps/api/test/planning.test.ts, apps/api/test/recipe.test.ts : les fixtures qui créent un `PlanningItem` directement via Prisma n'avaient pas le nouveau champ `portions` requis. - apps/web/cypress/e2e/planning-page.cy.ts : ajoute `portions` aux items mockés pour rester fidèle au contrat `PlanningItemView`. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
|||
| 8bdbfda3ae |
feat(planning): dialog de sélection de recette, création de planning, assignation avec portions
Ajoute le chaînon manquant entre le catalogue de recettes et le planning hebdomadaire : - Backend : `PlanningItem.portions` (nouvelle colonne + migration), `POST /planning/items` / `DELETE /planning/items/:id` (créent la semaine de planning à la volée si besoin), `GET /recipes` gagne les filtres `ingredientIds`/`dietIds` (ET) en plus de `suitableForHousehold` (déjà préparé). - Frontend : nouveau `Dialog` générique (premier modal de l'app), `RecipePickerDialog` qui réutilise le même affichage que le catalogue (`RecipeTabs`/`RecipeTable`) avec recherche par nom, filtre ingrédients, filtre régime alimentaire, toggle "convient à tout le foyer", puis une étape de saisie du nombre de portions. - `PlanningPage` : le bouton "+" de chaque case ouvre le dialog, le bouton "✕" retire la recette (optimiste, avec rollback si l'appel échoue), les portions s'affichent sur chaque chip. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |