feat(recipes): ingrédients hors-catalogue (placeholders) + revue admin #16

Merged
kyuno merged 1 commit from feat/off-catalog-ingredients into feat/admin-tech-steps 2026-08-29 09:42:58 +02:00
Owner

Contexte

Le catalogue Ingredient est seedé et fermé : dès qu'un ingrédient manque, la création manuelle bloquait l'utilisateur (IngredientPicker sans issue) et l'import perdait silencieusement la ligne (RecipeImportForm → choisir dans le catalogue ou écarter). Objectif : débloquer le workflow et rendre visible ce qui manque au catalogue.

Basé sur feat/admin-tech-steps (PR #15) — à retarger sur main une fois #15 mergée.

Modèle

Ingredient gagne isPlaceholder / displayName / createdById / createdAt / reviewedAt (migration 20260828140000_ingredient_placeholder, écrite à la main — Postgres indisponible en session). Un placeholder est une ligne Ingredient normale (RecipeIngredient inchangé), key = placeholder:<uuid>, exclue de GET /reference/ingredients et de ingredient-matcher.

API

  • recipeIngredientInputSchema : ingredientId XOR placeholderName (z.string().trim().min(1).max(120)).
  • createRecipeInternal / updateRecipe : resolveIngredientLines crée les lignes placeholder dans le $transaction de la recette, puis analytics.recordEvent("ingredient.placeholder_created"). Une ligne placeholder éditée refait l'aller-retour par son id réel (pas de doublon).
  • Nouveau module admin-catalog.{service,routes}.ts monté /admin/catalog (requireAdmin) :
    • GET /placeholders?reviewed= — regroupés par normalizePlaceholderName (minuscules + sans accents + ponctuation→espace), un groupe = un manque (recipeCount, recettes-échantillon, orthographes, firstSeenAt).
    • PATCH /placeholders/mark-reviewed — pose reviewedAt.
    • POST /placeholders/prune-orphans (+ scripts/prune-orphan-placeholders.ts) — supprime les placeholders sans recette.

Front apps/web

  • IngredientPicker : prop onAddPlaceholder → bouton « Ajouter « … » comme ingrédient à compléter » dans l'état vide (formulaire + import).
  • IngredientRow : badge « à compléter », pas de badges allergène/régime.
  • RecipeImportForm : 3ᵉ action « Garder tel quel » sur la liste « à compléter ».
  • ingredient-label.tsingredientLabel(ingredient, t) = displayName ?? t(catalog.ingredients.<key>), appliqué aux 7 sites de rendu de libellé.
  • placeholder-ingredient.tsIngredientView synthétique d'une ligne pas encore enregistrée (id 0 → soumise en placeholderName).

Front apps/admin-web

Page /catalogue (nav PackageSearch) : liste des manques (à traiter / archive), « Marquer comme traité » par groupe, « Purger les orphelins ».

Vérification

  • pnpm lint · pnpm -r build
  • Mocha pur normalizePlaceholderName 4/4
  • Cypress apps/admin-web (catalog.cy.ts + nav admin-layout) 6/6
  • Cypress apps/web : nouveau scénario placeholder ; planning / shopping-list / preferences / recipes / recipe-sources 19/19 (1 flake pré-existant sans rapport dans recipe-form.feature)
  • Suites Mocha DB-backed écrites (recipe-placeholder.test.ts, admin-catalog.test.ts) — non exécutées (pas de Postgres en session, comme les suites admin existantes) ; prisma migrate dev à lancer à la revue.

🤖 Generated with Claude Code

## Contexte Le catalogue `Ingredient` est seedé et fermé : dès qu'un ingrédient manque, la création manuelle bloquait l'utilisateur (`IngredientPicker` sans issue) et l'import perdait silencieusement la ligne (`RecipeImportForm` → choisir dans le catalogue **ou** écarter). Objectif : débloquer le workflow **et** rendre visible ce qui manque au catalogue. Basé sur `feat/admin-tech-steps` (PR #15) — à retarger sur `main` une fois #15 mergée. ## Modèle `Ingredient` gagne `isPlaceholder` / `displayName` / `createdById` / `createdAt` / `reviewedAt` (migration `20260828140000_ingredient_placeholder`, écrite à la main — Postgres indisponible en session). Un placeholder est une ligne `Ingredient` normale (`RecipeIngredient` inchangé), `key` = `placeholder:<uuid>`, exclue de `GET /reference/ingredients` et de `ingredient-matcher`. ## API - `recipeIngredientInputSchema` : `ingredientId` **XOR** `placeholderName` (`z.string().trim().min(1).max(120)`). - `createRecipeInternal` / `updateRecipe` : `resolveIngredientLines` crée les lignes placeholder dans le `$transaction` de la recette, puis `analytics.recordEvent("ingredient.placeholder_created")`. Une ligne placeholder éditée refait l'aller-retour par son id réel (pas de doublon). - Nouveau module `admin-catalog.{service,routes}.ts` monté `/admin/catalog` (`requireAdmin`) : - `GET /placeholders?reviewed=` — regroupés par `normalizePlaceholderName` (minuscules + sans accents + ponctuation→espace), un groupe = un manque (`recipeCount`, recettes-échantillon, orthographes, `firstSeenAt`). - `PATCH /placeholders/mark-reviewed` — pose `reviewedAt`. - `POST /placeholders/prune-orphans` (+ `scripts/prune-orphan-placeholders.ts`) — supprime les placeholders sans recette. ## Front `apps/web` - `IngredientPicker` : prop `onAddPlaceholder` → bouton « Ajouter « … » comme ingrédient à compléter » dans l'état vide (formulaire + import). - `IngredientRow` : badge « à compléter », pas de badges allergène/régime. - `RecipeImportForm` : 3ᵉ action « Garder tel quel » sur la liste « à compléter ». - `ingredient-label.ts` — `ingredientLabel(ingredient, t)` = `displayName ?? t(catalog.ingredients.<key>)`, appliqué aux 7 sites de rendu de libellé. - `placeholder-ingredient.ts` — `IngredientView` synthétique d'une ligne pas encore enregistrée (`id` 0 → soumise en `placeholderName`). ## Front `apps/admin-web` Page `/catalogue` (nav `PackageSearch`) : liste des manques (à traiter / archive), « Marquer comme traité » par groupe, « Purger les orphelins ». ## Vérification - `pnpm lint` ✅ · `pnpm -r build` ✅ - Mocha pur `normalizePlaceholderName` 4/4 ✅ - Cypress `apps/admin-web` (`catalog.cy.ts` + nav `admin-layout`) 6/6 ✅ - Cypress `apps/web` : nouveau scénario placeholder ✅ ; `planning` / `shopping-list` / `preferences` / `recipes` / `recipe-sources` 19/19 ✅ (1 flake pré-existant sans rapport dans `recipe-form.feature`) - Suites Mocha DB-backed écrites (`recipe-placeholder.test.ts`, `admin-catalog.test.ts`) — non exécutées (pas de Postgres en session, comme les suites admin existantes) ; `prisma migrate dev` à lancer à la revue. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
kyuno added 1 commit 2026-08-28 17:23:01 +02:00
feat(recipes): permet d'ajouter des ingredients hors-catalogue
Some checks failed
CI / lint (push) Successful in 2m33s
CI / build (push) Successful in 3m4s
CI / e2e (push) Successful in 7m50s
CI / intent-service-test (push) Successful in 11m32s
CI / test (push) Failing after 22m43s
a053371cf1
Quand le catalogue seede ne couvre pas un ingredient, l'utilisateur pouvait
etre bloque (creation manuelle) ou perdre silencieusement la ligne (import).
Une ligne de recette accepte desormais `placeholderName` (texte libre) au
lieu de `ingredientId` : l'API cree une ligne `Ingredient` `isPlaceholder`
(cle `placeholder:<uuid>`, `displayName`, `createdById`) dans la transaction
de la recette, et emet `ingredient.placeholder_created`. Ces lignes sont
exclues de `GET /reference/ingredients` et de `ingredient-matcher`.

Front : bouton "Ajouter << ... >>" dans l'etat vide de `IngredientPicker`
(formulaire + import), badge "a completer" sur la ligne, helper
`ingredientLabel` applique partout ou un libelle d'ingredient est rendu.

Admin : `/admin/catalog/*` (+ page `apps/admin-web`) liste les placeholders
regroupes par nom normalise, "marquer traite" (`reviewedAt`) et purge des
orphelins. La promotion en vraie entree catalogue reste manuelle.

Migration `ingredient_placeholder` ecrite a la main (Postgres indisponible).
Suites Mocha DB-backed ecrites, non executees en session ; test pur
`normalizePlaceholderName` + Cypress admin-web/web verts.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
kyuno force-pushed feat/off-catalog-ingredients from a053371cf1 to 5cc70d4244 2026-08-28 19:07:51 +02:00 Compare
kyuno force-pushed feat/off-catalog-ingredients from 5cc70d4244 to c246a42a77 2026-08-28 23:00:21 +02:00 Compare
kyuno force-pushed feat/off-catalog-ingredients from c246a42a77 to 16f593d5d0 2026-08-28 23:37:36 +02:00 Compare
kyuno merged commit f7aabd9dcc into feat/admin-tech-steps 2026-08-29 09:42:58 +02:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: kyuno/batchCooking#16
No description provided.