feat: pantry drag-to-recategorize, always-show categories, auto-categorize; fix category label + merge quantity mismatch (v0.84.0)
Pantry items can now be dragged between category sections (dnd-kit, cross-category drop only — no sortOrder column to persist within-category reorder). All 9 categories always render as sections, even empty ones, instead of only ones with items. Added an "Auto-categorize" action mirroring the shopping list's guessAisle heuristic. Fixed: the edit dialog's category dropdown displayed the raw stored slug/"__other__" instead of its translated label (SelectValue needs a value->label render function, same pattern shopping-list-view.tsx already used elsewhere). Fixed: merge-duplicates now merges by ingredient identity alone, not identity+unit — two rows merge even with mismatched, missing, or different-unit quantities, only summing when safe to do so. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+3
-3
@@ -69,9 +69,9 @@ Status legend: **Exists** (fully working) · **Partial** (works but with a real
|
||||
| Aisle categorization | Exists | Heuristic auto-assign + bulk re-categorize, plus inline custom-category creation and full drag-and-drop reordering (richer UI than previously documented) | `apps/web/lib/grocery-categories.ts` |
|
||||
| Grocery delivery integration | **Partial / stub** | Generic export payload works (integrator-shaped — no visible in-app UI consumer besides the Instacart adapter); Instacart adapter is an explicit stub (requires a partnership agreement Epicure doesn't have — returns 501 if unconfigured, throws if "configured") | `apps/web/lib/grocery-providers/instacart.ts` |
|
||||
| Other delivery/price integrations (DoorDash, Kroger, Walmart, live pricing) | **Missing** | Confirmed absent by repo-wide search | — |
|
||||
| Pantry manual CRUD | Exists (extended 2026-07-24) | Full edit dialog added (previously add+delete only, despite the API already supporting `PUT`) — name/quantity/unit/expiry plus two new fields, **notes** (free text) and **category** (same `GROCERY_CATEGORIES` slugs shopping lists use). List now groups into collapsible-by-category sections when more than one category is present, mirroring the shopping list's grouping UX (without the drag-reorder — pantry has no manual ordering need). Includes a bulk case-insensitive name+unit merge endpoint (used by the scan-confirm flow). | `apps/web/app/api/v1/pantry/**`, `apps/web/components/meal-plan/pantry-manager.tsx`, `apps/web/components/pantry/pantry-item-dialog.tsx` |
|
||||
| Ingredient alias matching (new 2026-07-24) | Exists | The `ingredients` table (canonical name + `aliases[]`) existed but was never populated or queried anywhere. Seeded with ~10 bilingual EN/FR staples (salt, sugar, pepper, flour, butter, milk, egg, onion, garlic, olive oil — `packages/db/src/seed.ts`) and wired into every place that compares ingredient names by raw text: pantry add/edit (sets `ingredientId` when a name/alias matches), the can-cook / "use it up soon" scorer, auto-deduct-on-cook, and shopping-list pantry-awareness on generation. Resolution happens at compare-time from free text (`resolveIngredientKey`), not from a stored FK on both sides — recipe ingredients still don't carry `ingredientId`. | `apps/web/lib/ingredient-match.ts`, `packages/db/src/seed.ts` |
|
||||
| Merge duplicate pantry items (new 2026-07-24) | Exists | One-shot cleanup for pre-existing pantry rows that are the same ingredient under different names (added before alias matching existed) — groups by resolved ingredient key + normalized unit, sums quantities only when every row in a group has a parseable one (otherwise keeps the first known amount rather than guessing), concatenates notes, keeps the soonest expiry. Manual trigger (a button in the pantry toolbar), not automatic — manual single-item add still always inserts a new row rather than silently merging, since two batches of the same ingredient can have different expiry dates worth tracking separately. | `apps/web/app/api/v1/pantry/merge-duplicates/route.ts` |
|
||||
| Pantry manual CRUD | Exists (extended 2026-07-24, twice) | Full edit dialog added (previously add+delete only, despite the API already supporting `PUT`) — name/quantity/unit/expiry plus **notes** (free text) and **category** (same `GROCERY_CATEGORIES` slugs shopping lists use; edit dialog's category dropdown initially displayed the raw slug/`__other__` instead of the translated label — fixed by passing a value→label render function to `SelectValue`, same pattern already used in `shopping-list-view.tsx`'s sort dropdown). List always renders all 9 category sections (8 `GROCERY_CATEGORIES` + Other) as collapsible groups, even empty ones — not just categories that currently have items — and items can be dragged between sections (dnd-kit `useDraggable`/`useDroppable`, cross-category drop only; no within-category manual reorder since pantry items have no `sortOrder` column to persist one). An "Auto-categorize" action (same `guessAisle` heuristic as the shopping list) fills in categories for uncategorized items in one click. Includes a bulk case-insensitive name+unit merge endpoint (used by the scan-confirm flow). | `apps/web/app/api/v1/pantry/**`, `apps/web/components/meal-plan/pantry-manager.tsx`, `apps/web/components/pantry/pantry-item-dialog.tsx` |
|
||||
| Ingredient alias matching (new 2026-07-24) | Exists | The `ingredients` table (canonical name + `aliases[]`) existed but was never populated or queried anywhere. Seeded with ~10 bilingual EN/FR staples (salt, sugar, pepper, flour, butter, milk, egg, onion, garlic, olive oil — `packages/db/src/seed.ts`) and wired into every place that compares ingredient names by raw text: pantry add/edit (sets `ingredientId` when a name/alias matches), the can-cook / "use it up soon" scorer, auto-deduct-on-cook, and shopping-list pantry-awareness on generation. Resolution happens at compare-time from free text (`resolveIngredientKey`), not from a stored FK on both sides — recipe ingredients still don't carry `ingredientId`. **No UI to add/edit aliases** — the seed list in `packages/db/src/seed.ts` is the only place today; growing it means editing that file and re-running `pnpm db:seed` (idempotent, `onConflictDoNothing` on name). | `apps/web/lib/ingredient-match.ts`, `packages/db/src/seed.ts` |
|
||||
| Merge duplicate pantry items (new 2026-07-24, relaxed same day) | Exists | One-shot cleanup for pre-existing pantry rows that are the same ingredient under different names (added before alias matching existed) — groups by resolved ingredient key **alone** (unit is deliberately not part of the grouping key, so two rows merge even with mismatched, missing, or differently-unit'd quantities); sums quantities only when every row in a group has a parseable quantity **and** the same unit, otherwise keeps the first known (quantity, unit) pair rather than guessing. Concatenates notes, keeps the soonest expiry. Manual trigger (a button in the pantry toolbar), not automatic — manual single-item add still always inserts a new row rather than silently merging, since two batches of the same ingredient can have different expiry dates worth tracking separately. | `apps/web/app/api/v1/pantry/merge-duplicates/route.ts` |
|
||||
| Auto-deduct pantry on cook | Exists (closed 2026-07-24) | Both UI callers that previously hardcoded `deductFromPantry: false` (`batch-cook-dishes.tsx`, `meal-planner.tsx`) now pass `true`. The new general "Mark cooked" feature (see below) additionally exposes it as a per-cook checkbox, default checked, rather than a silent always-on. Matching now goes through the ingredient-alias resolver, not a raw name string compare. | `apps/web/app/api/v1/recipes/[id]/cooked/route.ts`, `apps/web/components/recipe/batch-cook-dishes.tsx`, `apps/web/components/meal-plan/meal-planner.tsx` |
|
||||
| Cook log edit/delete (new 2026-07-24) | Exists | Plain (non-batch) cook-log entries can now be listed, edited (date/servings/notes), and removed — previously log-once, no way to fix a mistake or remove a duplicate entry. The recipe page's "Cooked N times" text is a hover tooltip (up to 8 dates, "+N more" beyond) that also opens a full manage sheet on click. Editing/deleting never touches pantry quantities — a deduction from when the entry was created isn't reversed or reapplied. | `apps/web/app/api/v1/recipes/[id]/cooked/[logId]/route.ts`, `apps/web/components/recipe/{mark-cooked-section,edit-cook-log-dialog}.tsx` |
|
||||
| Quantity display precision (closed 2026-07-24) | Exists | Pantry and shopping-list quantities are stored as `decimal(10,4)` and were displayed as the raw string (e.g. "0.3333 kg", "2.0000 kg") everywhere: in-app lists, print views, and Markdown exports. All 6 spots now go through `formatQuantity` (fraction-aware rounding, already used for recipe ingredient scaling) instead of the raw column value. | `apps/web/lib/fractions.ts`, `apps/web/components/meal-plan/{pantry-manager,shopping-list-view}.tsx`, `apps/web/app/print/{pantry,shopping-list/[id]}/page.tsx`, `apps/web/lib/markdown/{pantry,shopping-list}.ts` |
|
||||
|
||||
Reference in New Issue
Block a user