feat: shopping list rename/delete, item reorder+categories+search, ingredient-quantity parsing fix
- Fixed a real i18n bug: the checked-count line called the wrong translation namespace and rendered the literal key on screen - Shopping lists can now be renamed and deleted from both the list index and detail pages (API already supported delete; rename was net new) - Root-caused "long list UI is off": meal-plan-generated lists never set an aisle, so every item fell into one undifferentiated "Other" bucket despite the grouping UI existing. Added a keyword-based aisle guesser wired into list generation (fallback only, never overrides an explicit aisle) plus a one-click "auto-categorize" for existing lists - Items can now be reordered by drag-and-drop within a category (dnd-kit), recategorized via a dropdown, deleted, searched, and sorted (category / alphabetical / unchecked-first); searching flattens the grouped view - Fixed a separate bug: AI-generated ingredients sometimes embedded the quantity/unit in the name itself (e.g. "2 cups flour" as one string). Added extractIngredientQuantity() as a Zod transform at both recipe create/update routes (the choke point every creation path funnels through) to split it back out, plus schema descriptions on the AI ingredient schemas as a prevention layer New migration 0028 (shopping_list_items.sort_order), left unapplied like the others. Verified with typecheck, lint, and a clean --no-cache docker build. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,7 @@ import { requireSessionOrApiKey } from "@/lib/api-auth";
|
||||
import { deleteObject } from "@/lib/storage";
|
||||
import { dispatchWebhook } from "@/lib/webhooks";
|
||||
import { parseQuantity } from "@/lib/parse-quantity";
|
||||
import { extractIngredientQuantity } from "@/lib/extract-ingredient-quantity";
|
||||
|
||||
const UpdateRecipeSchema = z.object({
|
||||
title: z.string().min(1).max(200).optional(),
|
||||
@@ -31,6 +32,9 @@ const UpdateRecipeSchema = z.object({
|
||||
unit: z.string().max(50).optional(),
|
||||
note: z.string().max(500).optional(),
|
||||
order: z.number().int().default(0),
|
||||
}).transform((ing) => {
|
||||
const { rawName, quantity, unit } = extractIngredientQuantity(ing.rawName, ing.quantity, ing.unit);
|
||||
return { ...ing, rawName, quantity, unit };
|
||||
})).max(100).optional(),
|
||||
steps: z.array(z.object({
|
||||
instruction: z.string().min(1).max(2000),
|
||||
|
||||
@@ -6,6 +6,7 @@ import { requireSessionOrApiKey } from "@/lib/api-auth";
|
||||
import { checkAndIncrementTierLimit, TierLimitError } from "@/lib/tiers";
|
||||
import { dispatchWebhook } from "@/lib/webhooks";
|
||||
import { parseQuantity } from "@/lib/parse-quantity";
|
||||
import { extractIngredientQuantity } from "@/lib/extract-ingredient-quantity";
|
||||
|
||||
const CreateRecipeSchema = z.object({
|
||||
title: z.string().min(1).max(200),
|
||||
@@ -33,6 +34,9 @@ const CreateRecipeSchema = z.object({
|
||||
unit: z.string().max(50).optional(),
|
||||
note: z.string().max(500).optional(),
|
||||
order: z.number().int().default(0),
|
||||
}).transform((ing) => {
|
||||
const { rawName, quantity, unit } = extractIngredientQuantity(ing.rawName, ing.quantity, ing.unit);
|
||||
return { ...ing, rawName, quantity, unit };
|
||||
})).max(100).default([]),
|
||||
steps: z.array(z.object({
|
||||
instruction: z.string().min(1).max(2000),
|
||||
|
||||
Reference in New Issue
Block a user