type DiffIngredient = { rawName: string; quantity?: string | number | null; unit?: string | null }; type DiffStep = { instruction: string }; const norm = (s: string) => s.trim().toLowerCase(); /** True if an AI-adapted/varied recipe is functionally identical to its * source — same servings, same ingredients (name/quantity/unit, in order), * same steps. Used to skip persisting a "variation" that changed nothing, * which otherwise clutters recipe history with no-op duplicates. */ export function isRecipeUnchanged( original: { baseServings: number; ingredients: DiffIngredient[]; steps: DiffStep[] }, candidate: { baseServings: number; ingredients: DiffIngredient[]; steps: DiffStep[] } ): boolean { if (original.baseServings !== candidate.baseServings) return false; if (original.ingredients.length !== candidate.ingredients.length) return false; if (original.steps.length !== candidate.steps.length) return false; for (let i = 0; i < original.ingredients.length; i++) { const o = original.ingredients[i]!; const c = candidate.ingredients[i]!; if (norm(o.rawName) !== norm(c.rawName)) return false; if (String(o.quantity ?? "") !== String(c.quantity ?? "")) return false; if (norm(o.unit ?? "") !== norm(c.unit ?? "")) return false; } for (let i = 0; i < original.steps.length; i++) { if (norm(original.steps[i]!.instruction) !== norm(candidate.steps[i]!.instruction)) return false; } return true; }