feat: pantry notes/categories, ingredient-alias matching, cook-log edit/delete, fork-list popover (v0.83.0)
Pantry: notes + category fields (collapsible grouping like the shopping list), a "Merge duplicates" cleanup action, and fixed quantity display precision (was showing raw decimal(10,4) strings like "0.3333 kg" everywhere — pantry, shopping list, print views, Markdown exports). Ingredient-alias matching: the ingredients table (canonical name + aliases) existed but was never populated or used. Seeded ~10 bilingual EN/FR staples and wired resolution into pantry add/edit, can-cook scoring, auto-deduct-on-cook, and shopping-list pantry-awareness, so "sel"/"sel fin"/"table salt" are recognized as the same ingredient. Cook log: entries from "Mark cooked" can now be edited and deleted (previously log-only, no fix-a-mistake path). The "Cooked N times" text is a hover tooltip listing every date and opens a full manage sheet on click. Also: the "Forked by N others" backlink is now a click-to-open popover instead of an always-inline list. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE "pantry_items" ADD COLUMN "notes" text;--> statement-breakpoint
|
||||
ALTER TABLE "pantry_items" ADD COLUMN "aisle" text;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -463,6 +463,13 @@
|
||||
"when": 1784894764837,
|
||||
"tag": "0065_cooing_carnage",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 66,
|
||||
"version": "7",
|
||||
"when": 1784897120622,
|
||||
"tag": "0066_short_klaw",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -54,6 +54,12 @@ export const pantryItems = pgTable("pantry_items", {
|
||||
rawName: text("raw_name").notNull(),
|
||||
quantity: decimal("quantity", { precision: 10, scale: 4 }),
|
||||
unit: text("unit"),
|
||||
notes: text("notes"),
|
||||
// Same free-text-or-null category slug as shoppingListItems.aisle — lets
|
||||
// the pantry group items the same way the shopping list does. Null means
|
||||
// "uncategorized", grouped under an "Other" bucket in the UI, not a
|
||||
// migration gap (existing rows are never backfilled).
|
||||
aisle: text("aisle"),
|
||||
expiresAt: timestamp("expires_at"),
|
||||
createdAt: timestamp("created_at").notNull().defaultNow(),
|
||||
}, (t) => [
|
||||
|
||||
+32
-1
@@ -1,5 +1,23 @@
|
||||
import { db } from "./client";
|
||||
import { tierDefinitions } from "./schema";
|
||||
import { tierDefinitions, ingredients } from "./schema";
|
||||
|
||||
// Starter set of canonical ingredients + common EN/FR synonyms, so pantry
|
||||
// items and recipe ingredients written differently ("sel", "sel fin", "sel
|
||||
// de table", "table salt") can still be recognized as the same thing (see
|
||||
// lib/ingredient-match.ts). Deliberately small — grows over time as gaps
|
||||
// are found, not meant to be exhaustive on day one.
|
||||
const STAPLE_INGREDIENTS: { name: string; aliases: string[]; category: string }[] = [
|
||||
{ name: "salt", aliases: ["sel", "sel fin", "sel de table", "table salt", "fine salt", "sea salt", "sel de mer"], category: "spicesCondiments" },
|
||||
{ name: "sugar", aliases: ["sucre", "sucre blanc", "white sugar", "granulated sugar", "sucre en poudre"], category: "pantry" },
|
||||
{ name: "black pepper", aliases: ["pepper", "poivre", "poivre noir", "ground pepper", "poivre moulu"], category: "spicesCondiments" },
|
||||
{ name: "flour", aliases: ["farine", "all-purpose flour", "farine de blé", "plain flour", "wheat flour"], category: "pantry" },
|
||||
{ name: "butter", aliases: ["beurre", "unsalted butter", "beurre doux", "salted butter", "beurre demi-sel"], category: "dairyEggs" },
|
||||
{ name: "milk", aliases: ["lait", "whole milk", "lait entier", "lait demi-écrémé"], category: "dairyEggs" },
|
||||
{ name: "egg", aliases: ["eggs", "œuf", "oeuf", "œufs", "oeufs"], category: "dairyEggs" },
|
||||
{ name: "onion", aliases: ["oignon", "oignons", "yellow onion", "onions"], category: "produce" },
|
||||
{ name: "garlic", aliases: ["ail", "garlic clove", "gousse d'ail", "garlic cloves"], category: "produce" },
|
||||
{ name: "olive oil", aliases: ["huile d'olive", "extra virgin olive oil", "huile d'olive vierge extra"], category: "pantry" },
|
||||
];
|
||||
|
||||
async function seed() {
|
||||
console.log("Seeding tier definitions...");
|
||||
@@ -32,6 +50,19 @@ async function seed() {
|
||||
])
|
||||
.onConflictDoNothing();
|
||||
|
||||
console.log("Seeding staple ingredients (name/alias matching)...");
|
||||
await db
|
||||
.insert(ingredients)
|
||||
.values(
|
||||
STAPLE_INGREDIENTS.map((i) => ({
|
||||
id: crypto.randomUUID(),
|
||||
name: i.name,
|
||||
aliases: i.aliases,
|
||||
category: i.category,
|
||||
}))
|
||||
)
|
||||
.onConflictDoNothing({ target: ingredients.name });
|
||||
|
||||
console.log("Seed complete.");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user