- Add product search screen (/products/search) as primary add flow;
"Add" button on products list opens search, manual entry remains as fallback
- Add to shelf bottom sheet with AnimatedSwitcher success view (green checkmark)
and SnackBar confirmation on the search screen via onAdded callback
- Manual add (AddProductScreen) shows SnackBar on success before popping back
- Extend AddProductScreen with optional nutrition fields (calories, protein,
fat, carbs, fiber); auto-fills from catalog selection and auto-expands section
- Auto-upsert catalog product on backend when nutrition data is provided
without a primary_product_id, linking the user product to the catalog
- Add fiber_per_100g field to CatalogProduct model and CreateRequest
- Add 16 new L10n keys across all 12 locales (addProduct, addManually,
searchProducts, quantity, storageDays, addToShelf, nutritionOptional,
calories, protein, fat, carbs, fiber, productAddedToShelf, etc.)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Backend:
- Migration 002: product_recognition_jobs table with JSONB images column
and job_type CHECK ('receipt' | 'products')
- New Kafka topics: ai.products.paid / ai.products.free
- ProductJob model, ProductJobRepository (mirrors dish job pattern)
- itemEnricher extracted from Handler — shared by HTTP handler and worker
- ProductSSEBroker: PG LISTEN on product_job_update channel
- ProductWorkerPool: 5 workers, branches on job_type to call
RecognizeReceipt or RecognizeProducts per image in parallel
- Handler: RecognizeReceipt and RecognizeProducts now return 202 Accepted
instead of blocking; 4 new endpoints: GET /ai/product-jobs,
/product-jobs/history, /product-jobs/{id}, /product-jobs/{id}/stream
- cmd/worker: extended to run ProductWorkerPool alongside dish WorkerPool
- cmd/server: wires productJobRepository + productSSEBroker; both SSE
brokers started in App.Start()
Flutter client:
- ProductJobCreated, ProductJobResult, ProductJobSummary, ProductJobEvent
models + submitReceiptRecognition/submitProductsRecognition/stream methods
- Shared _openSseStream helper eliminates duplicate SSE parsing loop
- ScanScreen: replace blocking AI calls with async submit + navigate to
ProductJobWatchScreen
- ProductJobWatchScreen: watches SSE stream, navigates to /scan/confirm
when done, shows error on failure
- ProductsScreen: prepends _RecentScansSection (hidden when empty); compact
horizontal list of recent scans with "See all" → history
- ProductJobHistoryScreen: full list of all product recognition jobs
- New routes: /scan/product-job-watch, /products/job-history
- L10n: 7 new keys in all 12 ARB files
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Generate recipes in English (reverted prompt to English-only)
- Add TranslateRecipes to OpenAI client (translate.go) — sends compact
JSON payload of translatable fields, merges back into original recipes
- recommendation/handler.go: translate recipes in-memory before response
when lang != "en"; falls back to English on error
- dish/repository.go: Create() now returns (dishID, recipeID, err) so
callers can upsert dish_translations after saving
- menu/handler.go: saveRecipes returns savedRecipeEntry slice with dishID;
saveDishTranslations calls TranslateRecipes then UpsertTranslation for
each dish when the request locale is not English
- savedrecipe/repository.go: updated to ignore dishID from Create()
- init.go: wire openaiClient as RecipeTranslator and dishRepository as
DishTranslator for menu.NewHandler
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Inserts a new PlanProductsSheet as step 1 of the planning flow.
Users see their current products as a multi-select checklist (all
selected by default) before choosing the planning mode and dates.
- Empty state explains the benefit and offers "Add products" CTA
while always allowing "Plan without products" to skip
- Selected product IDs flow through PlanMenuSheet →
PlanDatePickerSheet → MenuService.generateForDates → backend
- Backend: added ProductIDs field to generate-menu request body;
uses ListForPromptByIDs when set, ListForPrompt otherwise
- Backend: added Repository.ListForPromptByIDs (filtered SQL query)
- All 12 ARB locale files updated with planProducts* keys
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Backend fixes:
- migration 003: add 'menu' value to recipe_source enum (was causing SQLSTATE 22P02)
- migration 004: rename recipe_products→recipe_ingredients, product_id→ingredient_id (was causing SQLSTATE 42P01)
- dish/repository.go: fix INSERT INTO tags using $1/$1 for two columns → $1/$2 (was causing SQLSTATE 42P08)
- home/handler.go: replace non-existent saved_recipes table with correct joins (recipes→dishes→dish_translations, user_saved_recipes) so today's plan and recommendations load correctly
- reqlog: new slog.Handler wrapper that adds request_id and stack trace to ERROR-level logs
- all handlers: slog.Error→slog.ErrorContext so error logs include request context; writeError includes request_id in response body
Client:
- home_screen.dart: extend home screen to future dates, show planned meals as ghost entries
- l10n: add new localisation keys for home screen date navigation and planned meal UI
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Backend:
- GET /dishes/search — hybrid FTS (english + simple) + trgm + ILIKE search
- GET /diary/recent — recently used dishes and products for the current user
- product search upgraded: FTS on canonical_name and product_aliases, ranked by GREATEST(ts_rank, similarity)
- importoff: collect product_name_ru/de/fr/... as product_aliases for multilingual search (e.g. "сникерс" → "Snickers")
- migrations: FTS + trgm indexes merged into 001_initial_schema.sql (002 removed)
Flutter:
- FoodSearchSheet: debounced search field, recently-used section, product/dish results, scan-photo and barcode chips
- DishPortionSheet: quick ½/1/1½/2 buttons + custom input
- + button in meal card now opens FoodSearchSheet instead of going directly to AI scan
- 7 new l10n keys across all 12 languages
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Rename catalog: ingredient/* → product/* (canonical_name, barcode, nutrition per 100g)
- Rename pantry: product/* → userproduct/* (user-owned items with expiry)
- Squash migrations into single 001_initial_schema.sql (clean-db baseline)
- product_categories: add English canonical name column; fix COALESCE in queries
- Remove product_translations: product names are stored in their original language
- Add default_unit_name to product API responses via unit_translations JOIN
- Add cmd/importoff: bulk import from OpenFoodFacts JSONL dump (COPY + ON CONFLICT)
- Diary: support product_id entries alongside dish_id (CHECK num_nonnulls = 1)
- Home: getLoggedCalories joins both recipes and catalog products
- Flutter: rename models/providers/services to match backend rename
- Flutter: add barcode scan flow for diary (mobile_scanner, product_portion_sheet)
- Flutter: localise 6 new keys across 12 languages (barcode scan, portion weight)
- Routes: GET /products/search, GET /products/barcode/{barcode}, /user-products
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Backend:
- Rename recognition_jobs → dish_recognition_jobs; add target_date and
target_meal_type columns to capture scan context at submission time
- Add job_id FK on meal_diary so entries are linked to their origin job
- New GET /ai/jobs endpoint returns today's unlinked jobs for the current user
- diary.Entry and CreateRequest gain job_id field; repository reads/writes it
- CORS middleware: allow Accept-Language and Cache-Control headers
- Logging middleware: implement http.Flusher on responseWriter (needed for SSE)
- Consolidate migrations into a single 001_initial_schema.sql
Flutter:
- POST /ai/recognize-dish now sends target_date and target_meal_type
- DishResultSheet accepts jobId; _addToDiary includes it in the diary payload,
saves last-used meal type to SharedPreferences, invalidates todayJobsProvider
- TodayJobsNotifier + todayJobsProvider: loads unlinked jobs via GET /ai/jobs
- Home screen shows _TodayJobsWidget (up to 3 tiles) between macros and meals;
tapping a done tile reopens DishResultSheet with the stored result
- Quick Actions row: third button "История" → /scan/history
- New RecognitionHistoryScreen: full-screen list of today's unlinked jobs
- LocalPreferences wrapper over SharedPreferences (last_used_meal_type)
- app_theme: apply Google Fonts Roboto as default font family
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace dual-consumer priority WorkerPool with a single consumer per
worker process. WORKER_PLAN=paid|free selects the Kafka topic and
consumer group ID (dish-recognition-paid / dish-recognition-free).
docker-compose now runs worker-paid and worker-free as separate services
for independent scaling. Makefile dev target launches both workers locally.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove denormalized columns (name, calories, protein_g, fat_g, carbs_g)
from meal_diary. Name is now resolved via JOIN with dishes/dish_translations;
macros are computed as recipe.*_per_serving * portions at query time.
- Add dish.Repository.FindOrCreateRecipe: finds or creates a minimal recipe
stub seeded with AI-estimated macros
- recognition/handler: resolve recipe_id synchronously per candidate;
simplify enrichDishInBackground to translations-only
- diary/handler: accept dish_id OR name; always resolve recipe_id via
FindOrCreateRecipe before INSERT
- diary/entity: DishID is now non-nullable string; CreateRequest drops macros
- diary/repository: ListByDate and Create use JOIN to return computed macros
- ai/types: add RecipeID field to DishCandidate
- Update tests and wire_gen accordingly
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Backend:
- Translate all recognition prompts (receipt, products, dish) from Russian to English
- Add lang parameter to Recognizer interface and pass locale.FromContext in handlers
- DishResult type uses candidates array for multi-candidate responses
Client:
- Add meal tracking: diary provider, date selector, meal type model
- DishResult parser: backward-compatible with legacy flat format and new candidates format
- DishResultScreen: sticky bottom button, full-width portion/meal-type inputs,
КБЖУ disclaimer moved under nutrition card, add date field to diary POST body
- Recognition prompts now return dish/product names in user's preferred language
- Onboarding, profile, home screen visual updates
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Introduce 6-step onboarding screen (Goal → Gender → DOB → Height+Weight
→ Activity → Calories) with per-step accent colors, hero illustration
area (concentric circles + icon), and white card content panel.
Backend user entity and service updated to support onboarding fields
(goal, activity, height, weight, DOB, dailyCalories). Router guards
unauthenticated and onboarding-incomplete users. Profile service and
screen updated to expose language and onboarding preferences.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>