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>
The recipes tab (recommendations + saved recipes + detail screen) is
removed from the app UI and all feature files are deleted. The section
will be redesigned from scratch.
- Remove /recipes and /recipe-detail routes from app_router.dart
- Remove Recipes tab from BottomNavigationBar
- Delete client/lib/features/recipes/ entirely
- Keep shared/models/recipe.dart and saved_recipe.dart for later reuse
- Add "Раздел рецептов (переработка с нуля)" section to docs/TODO.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
On first open the ListView started at offset 0 (future end), leaving
today's pill out of view. Also, navigation always scrolled the pill to
the left edge instead of centering it.
Added _centeredOffset() helper and a post-frame jumpTo in initState()
so the initially selected date is centered immediately. Updated
_jumpToToday() and didUpdateWidget() to use the same centering formula.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix _isoWeek: correct Sunday shift (-3 instead of +4), use floor+1 formula,
match jan1 timezone to input — planned meals now appear correctly for UTC+ users
- Add kPlanningHorizonDays=28 / kMenuPastWeeks=8 constants; apply to home date
strip, plan picker (strip + calendar), and menu screen prev/next navigation
- Menu screen week nav: disable arrows at min/max limits using compareTo
- Home screen: replace _GenerateActionCard/_WeekPlannedChip conditional with
always-visible _FutureDayPlanButton(dateString); show _DayPlannedChip only
when the specific day has planned meals; remove standalone _PlanMenuButton
- _FutureDayPlanButton uses selected date as defaultStart instead of lastPlanned+1
- Rename weekPlannedLabel -> dayPlannedLabel across all 12 locales
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>
Phase 1: date strip now covers today + 7 future days; right chevron enabled;
future pills rendered in lighter style.
Phase 2: home screen shows DateContext (past/today/future):
- future dates: hide calorie ring + macros, show PlanningBanner
- plannedMealsProvider derives from cached menuProvider (no extra API call)
- _MealCard shows ghost PlannedSlotTile for unconfirmed menu slots
- "Mark as eaten" creates a diary entry (source: menu_plan) via existing API
New l10n keys (12 locales): planningForDate, markAsEaten, plannedMealLabel
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>
Two bugs caused a FK constraint violation on products.unit:
1. RecognizedItem.fromJson fell back to 'шт' (Cyrillic, not a valid
units.code) when the AI returned a null unit — changed to 'pcs'.
2. The unit dropdown in RecognitionConfirmScreen displayed units.keys.first
for invalid units but never updated item.unit, so the invalid value was
still submitted. Added a reconcile step in build() that syncs item.unit
to units.keys.first whenever the stored value is not in the valid set.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace all hardcoded Russian strings in ScanScreen and _LoadingDialog
with AppLocalizations keys; add addFromReceiptOrPhoto, chooseMethod,
photoReceipt, photoReceiptSubtitle, photoProducts, photoProductsSubtitle,
recognizing keys to all 12 ARB files and regenerate AppLocalizations.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add flutter_localizations + intl, 12 ARB files (en/ru/es/de/fr/it/pt/zh/ja/ko/ar/hi),
replace all hardcoded Russian UI strings with AppLocalizations, detect system locale
on first launch, localise bottom nav bar labels, document rule in CLAUDE.md.
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>
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>
Remove "Определить блюдо" from ScanScreen and the /scan/dish route.
The + button on each meal card now triggers dish recognition inline —
picks image, shows loading dialog, then presents DishResultSheet as a
modal bottom sheet. After adding to diary the sheet closes and the user
stays on home.
Also fix Navigator.pop crash: showDialog uses the root navigator by
default, so capture Navigator.of(context, rootNavigator: true) before
the async gap and use it to close the loading dialog.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace static 7-day ListView with a scrollable strip (365 days back,
today at the right edge) and a header row with:
- chevron buttons to step one day at a time
- selected date label ("Вт, 18 марта")
- "Сегодня" shortcut that appears when a past date is selected
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>
The home screen CaloriesCard now uses a circular ring (CustomPainter)
instead of a LinearProgressIndicator. Ring colour is determined by the
user's goal type (lose / maintain / gain) with inverted semantics for
the gain goal — red means undereating, not overeating.
Overflow beyond 100% is shown as a thinner second-lap arc in the same
warning colour. Numbers (logged kcal, goal, remaining/overage) are
displayed both inside the ring and in a stat column to the right.
Adds docs/calorie_ring_color_spec.md with the full colour threshold
specification per goal type.
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>
- Add units + unit_translations tables with FK constraints on products and ingredient_mappings
- Normalize products.unit from Russian strings (г, кг) to English codes (g, kg)
- Load units at startup (in-memory registry) and serve via GET /units (language-aware)
- Replace hardcoded _units lists and _mapUnit() functions in Flutter with unitsProvider FutureProvider
- Re-fetches automatically when language changes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- migration 013: create languages table (code PK, native_name, english_name,
is_active, sort_order) with all 12 existing languages seeded
- locale: add Language struct, Languages []Language, LoadFromDB() — queries
languages table at startup and populates both Supported map and Languages
slice; existing Parse/FromContext/FromRequest unchanged
- main.go: call locale.LoadFromDB after pool is ready
- gemini/recipe.go: remove hardcoded langNames map, use locale.Languages
linear lookup for English name in prompt
- language/handler.go: new package with GET /languages handler returning
active languages list (no auth required)
- server.go: register GET /languages as public route
- Flutter: add LanguageRepository + languageRepositoryProvider that fetches
/languages from backend
- language_provider.dart: replace const supportedLanguages map with
supportedLanguagesProvider (FutureProvider) backed by LanguageRepository
- profile_provider.dart: remove supportedLanguages.containsKey validation —
backend is source of truth; sync any non-empty language from preferences
- profile_screen.dart: use supportedLanguagesProvider for display name and
dropdown (async with loading/error states)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Store date_of_birth (DATE) instead of a static age integer so that age
is always computed dynamically from the stored date of birth.
- Migration 011: adds date_of_birth, backfills from age, drops age
- AgeFromDOB helper computes current age from YYYY-MM-DD string
- User model, repository SQL, and service validation updated
- Flutter: User.age becomes a computed getter; profile edit screen
uses a date picker bounded to [today-120y, today-10y]
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Passing the outer widget context to Navigator.pop() inside a dialog or
bottom sheet builder caused GoRouter to pop a page route instead of the
modal, triggering the "no pages left to show" assertion.
Affects _showChangeDialog and _confirmGenerate in menu_screen.dart,
and _showAddMenu bottom sheet in products_screen.dart.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add languageProvider (StateProvider<String>, default 'ru') with
supportedLanguages map matching backend locale.Supported
- Wire Accept-Language header into AuthInterceptor via languageGetter
callback; all API requests now carry the current language
- Sync language from user profile preferences into languageProvider
on every ProfileNotifier load/update
- Add language field to UpdateProfileRequest, serialized as
preferences.language in PUT /profile
- Profile screen: НАСТРОЙКИ section displays current language;
edit sheet adds DropdownButtonFormField for language selection
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Navigator.pop(context) with the outer tile context caused go_router to
pop the main navigation stack instead of closing the dialog. Use the
builder's ctx parameter instead.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add ProfileService (GET/PUT /profile), ProfileNotifier provider,
and full ProfileScreen with body-params, goal/activity, daily-calories
sections and logout confirmation. EditProfileSheet lets user update
name, height, weight, age, gender, goal and activity; backend
auto-recalculates daily_calories via Mifflin-St Jeor.
HomeScreen greeting now shows the user's real name from profileProvider.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Switch AppColors to iOS system palette (007AFF blue, F2F2F7 grouped
background, separator, label hierarchy) and rewrite AppTheme with
iOS-inspired Material 3 tokens (no elevation, negative letter-spacing,
50px buttons, 12px radii). Replace removed primaryLight/accent references
in recipe screens with primary.withValues(alpha:0.15) and primary.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Backend:
- internal/home: GET /home/summary endpoint
- Today's meal plan from menu_plans/menu_items
- Logged calories sum from meal_diary
- Daily goal from user profile (default 2000)
- Expiring products within 3 days
- Last 3 saved recommendations (no AI call on home load)
- Wire homeHandler in server.go and main.go
Flutter:
- shared/models/home_summary.dart: HomeSummary, TodaySummary,
TodayMealPlan, ExpiringSoon, HomeRecipe
- features/home/home_service.dart + home_provider.dart
- features/home/home_screen.dart: greeting, calorie progress bar,
today's meals card, expiring banner, quick actions row,
recommendations horizontal list
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace decommissioned llama-3.2-11b-vision-preview with
meta-llama/llama-4-scout-17b-16e-instruct (Groq deprecation)
- Use XFile.readAsBytes() instead of File(path).readAsBytes() so
Android content URIs (from gallery picks) are read correctly
- Add maxWidth/maxHeight constraints to image picker calls to reduce
payload size
- Increase receiveTimeout from 30s to 120s to accommodate slow vision AI
- Log recognition errors via debugPrint instead of swallowing them
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Backend (Go):
- Project structure with chi router, pgxpool, goose migrations
- JWT auth (access/refresh tokens) with Firebase token verification
- NoopTokenVerifier for local dev without Firebase credentials
- PostgreSQL user repository with atomic profile updates (transactions)
- Mifflin-St Jeor calorie calculation based on profile data
- REST API: POST /auth/login, /auth/refresh, /auth/logout, GET/PUT /profile, GET /health
- Middleware: auth, CORS (localhost wildcard), logging, recovery, request_id
- Unit tests (51 passing) and integration tests (testcontainers)
- Docker Compose setup with postgres healthcheck and graceful shutdown
Flutter client:
- Riverpod state management with GoRouter navigation
- Firebase Auth (email/password + Google sign-in with web popup support)
- Platform-aware API URLs (web/Android/iOS)
- Dio HTTP client with JWT auth interceptor and concurrent refresh handling
- Secure token storage
- Screens: Login, Register, Home (tabs: Menu, Recipes, Products, Profile)
- Unit tests (17 passing)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>