feat: cooking history/gallery, unit conversion, nutrition diary, pantry scan, digest cron, nutrition-targeted meal plans
Six M-sized items from HANDOFF.md's new-features backlog: - Profile tabs: cooking-history stats (total cooked, last-cooked, streak) and a "cooked it" photo gallery, both owner-only - Display-time unit conversion (metric<->imperial) for recipe ingredients, respecting each user's unitPref; original value always shown alongside the conversion - Nutrition daily diary: per-day macro totals computed from cooking history x recipe nutritionData, compared against user goals - Pantry scan: real barcode lookup (zxing + Open Food Facts, no API key) with an AI-vision fallback for unbarcoded items, always confirm-before- insert, both paths tier/rate-limited like other AI features - Weekly digest email: new followers/comments/ratings + trending recipes, sent via a new `cron` Docker stage (alpine+crond+curl) and `digest-cron` compose service hitting a bearer-token-protected internal route - Meal-plan generation can now target a user's nutrition goals as a prompt-level nudge (recipes are AI-invented, not DB-sourced, so this can't be a hard macro constraint) Caught a real deploy-breaking issue while adding the cron stage: appending it after `runner` silently changed the Dockerfile's default build target, and `web`'s compose config didn't pin one — fixed by pinning `target: runner` explicitly. Verified with typecheck, lint, and three separate `docker build --target` runs (runner/cron/migrator) plus `docker compose config` validation. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,7 @@
|
||||
"feed": "Feed",
|
||||
"collections": "Collections",
|
||||
"mealPlan": "Meal Plan",
|
||||
"nutrition": "Nutrition",
|
||||
"pantry": "Pantry",
|
||||
"shopping": "Shopping",
|
||||
"settings": "Settings",
|
||||
@@ -303,7 +304,8 @@
|
||||
},
|
||||
"nutritionGoals": {
|
||||
"title": "Daily Nutrition Goals",
|
||||
"description": "Set your daily targets. These are shown as progress bars on your meal plan."
|
||||
"description": "Set your daily targets. These are shown as progress bars on your meal plan.",
|
||||
"viewDiaryCta": "View nutrition diary"
|
||||
},
|
||||
"webhooksPage": {
|
||||
"title": "Webhooks",
|
||||
@@ -398,6 +400,27 @@
|
||||
"fiber": "Fiber",
|
||||
"sodium": "Sodium"
|
||||
},
|
||||
"nutritionDiary": {
|
||||
"title": "Nutrition Diary",
|
||||
"subtitle": "Your daily nutrition intake, computed from what you've cooked.",
|
||||
"dateLabel": "Date",
|
||||
"today": "Today",
|
||||
"loading": "Loading…",
|
||||
"loadError": "Couldn't load your nutrition diary. Please try again.",
|
||||
"goalNotSet": "You haven't set daily nutrition goals yet.",
|
||||
"setGoalsCta": "Set goals",
|
||||
"calories": "Calories",
|
||||
"protein": "Protein",
|
||||
"carbs": "Carbs",
|
||||
"fat": "Fat",
|
||||
"fiber": "Fiber",
|
||||
"sodium": "Sodium",
|
||||
"entriesTitle": "Recipes cooked",
|
||||
"noEntries": "Nothing logged for this day. Mark a recipe as \"cooked\" to see it here.",
|
||||
"servingsLabel": "{count, plural, one {1 serving} other {{count} servings}}",
|
||||
"nutritionUnknownBadge": "Nutrition unknown",
|
||||
"unknownNote": "{count, plural, one {1 entry has no nutrition data and isn't included in the totals above.} other {{count} entries have no nutrition data and aren't included in the totals above.}}"
|
||||
},
|
||||
"explore": {
|
||||
"title": "Explore",
|
||||
"searchPlaceholder": "Search public recipes…",
|
||||
@@ -430,12 +453,11 @@
|
||||
"copyMarkdown": "Copy as Markdown",
|
||||
"downloadMarkdown": "Download as Markdown",
|
||||
"copiedToClipboard": "Copied to clipboard",
|
||||
"copyFailed": "Failed to copy",
|
||||
"copyFailed": "Failed to copy to clipboard",
|
||||
"print": "Print",
|
||||
"share": "Share",
|
||||
"deleteFailed": "Delete failed",
|
||||
"updateFailed": "Update failed",
|
||||
"copyFailed": "Failed to copy to clipboard",
|
||||
"deleted": "Deleted",
|
||||
"cancel": "Cancel",
|
||||
"delete": "Delete",
|
||||
@@ -646,6 +668,8 @@
|
||||
"includePantryItems": "Include pantry items",
|
||||
"pantryMode": "Pantry mode — maximize use of what you have",
|
||||
"pantryModeDescription": "Prefer meals that use multiple pantry ingredients and minimize extra shopping.",
|
||||
"targetNutritionGoals": "Target my nutrition goals",
|
||||
"targetNutritionGoalsDescription": "Nudge the AI to aim for your daily calorie and macro targets across the day's meals.",
|
||||
"removeFailed": "Failed to remove",
|
||||
"generationFailed": "Generation failed",
|
||||
"anyDifficulty": "Any",
|
||||
@@ -691,7 +715,26 @@
|
||||
"colItem": "Item",
|
||||
"colQuantity": "Quantity",
|
||||
"colExpires": "Expires",
|
||||
"expiredDaysAgo": "Expired {days}d ago"
|
||||
"expiredDaysAgo": "Expired {days}d ago",
|
||||
"scan": "Scan",
|
||||
"scanTitle": "Add by scan",
|
||||
"scanDescription": "Scan a barcode or take a photo to quickly add pantry items.",
|
||||
"scanBarcodeOption": "Scan barcode",
|
||||
"scanPhotoOption": "Take/upload photo",
|
||||
"scanBarcodeTitle": "Scan barcode",
|
||||
"scanBarcodeDescription": "Point your camera at a product barcode.",
|
||||
"scanConfirmTitle": "Confirm item",
|
||||
"scanConfirmDescription": "Review and edit before adding to your pantry.",
|
||||
"scanAnalyzingTitle": "Analyzing photo",
|
||||
"scanAnalyzingDescription": "Identifying items in your photo…",
|
||||
"scanBack": "Back",
|
||||
"scanAddToPantry": "Add to pantry",
|
||||
"scanAdded": "Added to pantry",
|
||||
"scanLookupFailed": "Barcode lookup failed",
|
||||
"scanNotFound": "Product not found. Try a photo scan instead.",
|
||||
"scanPhotoFailed": "Photo scan failed",
|
||||
"scanNoItemsFound": "No items recognized in this photo",
|
||||
"cameraError": "Couldn't access the camera"
|
||||
},
|
||||
"feed": {
|
||||
"title": "Feed",
|
||||
@@ -984,5 +1027,21 @@
|
||||
"privateBio": "AI context (private)",
|
||||
"privateBioDescription": "Never shown publicly. Injected into AI prompts to personalise suggestions — add your dietary preferences, kitchen equipment, cooking skill level, allergies, etc.",
|
||||
"privateBioPlaceholder": "e.g. I'm vegetarian, have a stand mixer and an air fryer, intermediate cook, allergic to tree nuts, prefer Mediterranean flavours…"
|
||||
},
|
||||
"profilePage": {
|
||||
"tabRecipes": "Recipes",
|
||||
"tabHistory": "Cooking History",
|
||||
"tabPhotos": "Photos",
|
||||
"statTotalCooked": "Total times cooked",
|
||||
"statStreak": "Current streak",
|
||||
"statLastCooked": "Last cooked",
|
||||
"streakDays": "{count, plural, one {# day} other {# days}}",
|
||||
"noneYet": "None yet",
|
||||
"recentActivity": "Recent activity",
|
||||
"noHistoryYet": "No cooking history yet.",
|
||||
"noPhotosYet": "No photos yet.",
|
||||
"previous": "Previous",
|
||||
"next": "Next",
|
||||
"pageOf": "Page {page} of {total}"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user