GourMerge
Git for recipes: branches, commits, forks and diffable merge requests, applied to cooking.
Full GDPR self-service built on a GitHub-style ghost tombstone account, so hard-deleting a user never breaks referential integrity.
Nuxt 4.5 · Vue 3.5 · Drizzle ORM · MySQL 8.4 · TipTap 3 · Playwright
Diffing two recipe commits by indexing both sides, not walking a tree GourMerge · server/utils/diff.ts
export function computeIngredientDiff(
oldIngredients: CommitIngredient[],
newIngredients: CommitIngredient[]
): IngredientDiffEntry[] {
const oldMap = new Map<number, IngredientSnapshot>()
const newMap = new Map<number, IngredientSnapshot>()
for (const ing of normalizeIngredients(oldIngredients)) {
oldMap.set(ing.ingredientId, ing)
}
for (const ing of normalizeIngredients(newIngredients)) {
newMap.set(ing.ingredientId, ing)
}
const result: IngredientDiffEntry[] = []
for (const [id, oldIng] of oldMap) {
const newIng = newMap.get(id)
if (!newIng) {
result.push({ type: 'removed', ingredientId: id, /* ... */ })
} else {
const changes: ('quantity' | 'unit')[] = []
if (oldIng.quantity !== newIng.quantity) changes.push('quantity')
if (oldIng.unit !== newIng.unit) changes.push('unit')
// ... push 'modified' when changes.length > 0, else 'unchanged'
}
}
// ... a second pass picks up ids present only in newMap
}