13 Commits

Author SHA1 Message Date
dbastrikin
8d33a4eb30 feat: product search screen with catalog add and success feedback
- 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>
2026-03-26 14:04:58 +02:00
dbastrikin
205edbdade feat: rename ingredients→products, products→user_products; add barcode/OFF import
- 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>
2026-03-21 12:45:48 +02:00
dbastrikin
54b10d51e2 feat: Flutter client localisation (12 languages)
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>
2026-03-19 22:22:52 +02:00
dbastrikin
87ef2097fc feat: meal tracking, dish recognition UX improvements, English AI prompts
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>
2026-03-17 14:29:36 +02:00
dbastrikin
ddbc8e2bc0 feat: add onboarding flow with visual redesign
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>
2026-03-16 23:13:00 +02:00
dbastrikin
61feb91bba feat: core schema redesign — dishes, structured recipes, cuisines, tags (iteration 7)
Replaces the flat JSONB-based recipe schema with a normalized relational model:

Schema (migrations consolidated to 001_initial_schema + 002_seed_data):
- New: dishes, dish_translations, dish_tags — canonical dish catalog
- New: cuisines, tags, dish_categories with _translations tables + full seed data
- New: recipe_ingredients, recipe_steps with _translations (replaces JSONB blobs)
- New: user_saved_recipes thin bookmark (drops saved_recipes + saved_recipe_translations)
- New: product_ingredients M2M table
- recipes: now a cooking variant of a dish (dish_id FK, no title/JSONB columns)
- recipe_translations: repurposed to per-language notes only
- products: mapping_id → primary_ingredient_id
- menu_items: recipe_id FK → recipes; adds dish_id
- meal_diary: adds dish_id, recipe_id → recipes, portion_g

Backend (Go):
- New packages: internal/cuisine, internal/tag, internal/dish (registry + handler + repo)
- New GET /cuisines, GET /tags (public), GET /dishes, GET /dishes/{id}, GET /recipes/{id}
- recipe, savedrecipe, menu, diary, product, ingredient packages updated for new schema

Flutter:
- New models: Cuisine, Tag; new providers: cuisineNamesProvider, tagNamesProvider
- recipe.dart: RecipeIngredient gains unit_code + effectiveUnit getter
- saved_recipe.dart: thin model, manual fromJson, computed nutrition getter
- diary_entry.dart: adds dishId, recipeId, portionG
- recipe_detail_screen.dart: localized cuisine/tag names via providers

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-15 18:01:24 +02:00
dbastrikin
a225f6c47a refactor: migrate ingredient aliases/categories to dedicated tables, drop spoonacular_id
- migration 012: create ingredient_categories + ingredient_category_translations
  tables (7 slugs, Russian names); add ingredient_aliases (ingredient_id, lang,
  alias) with GIN trigram index; migrate aliases JSONB from ingredient_mappings
  and ingredient_translations; drop aliases columns and spoonacular_id; add
  UNIQUE (canonical_name) as conflict key
- ingredient/model: remove SpoonacularID, add CategoryName for localized display
- ingredient/repository: conflict on canonical_name; GetByID/Search join category
  translations and aliases lateral; new UpsertAliases (pgx batch),
  UpsertCategoryTranslation; remove GetBySpoonacularID; split scan helpers into
  scanMappingWrite / scanMappingRead
- gemini/recognition: add IngredientTranslation type; IngredientClassification
  now carries Translations []IngredientTranslation instead of CanonicalNameRu;
  update ClassifyIngredient prompt to English with structured translations array
- recognition/handler: update ingredientRepo interface; saveClassification uses
  UpsertAliases and iterates Translations
- recipe/model: remove SpoonacularID from RecipeIngredient
- integration tests: remove SpoonacularID fixtures, replace GetBySpoonacularID
  tests with GetByID, add UpsertAliases and UpsertCategoryTranslation tests
- Flutter: remove canonicalNameRu from IngredientMapping, add categoryName;
  displayName returns server-resolved canonicalName; regenerate .g.dart

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10 14:40:07 +02:00
dbastrikin
c9ddb708b1 feat: replace age integer with date_of_birth across backend and client
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>
2026-03-09 23:37:58 +02:00
dbastrikin
9530dc6ff9 feat: implement Iteration 5 — home screen dashboard
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>
2026-02-22 15:25:28 +02:00
dbastrikin
ea8e207a45 feat: implement Iteration 4 — menu planning, shopping list, diary
Backend:
- Migrations 007 (menu_plans, menu_items, shopping_lists) and
  008 (meal_diary)
- gemini/menu.go: GenerateMenu — 7-day × 3-meal plan via one Groq call
- internal/menu: model, repository (GetByWeek, SaveMenuInTx,
  shopping list CRUD), handler (GET/PUT/DELETE /menu,
  POST /ai/generate-menu, shopping list endpoints)
- internal/diary: model, repository, handler (GET/POST/DELETE /diary)
- Increase server WriteTimeout to 120s for long AI calls
- api_client.go: add patch() and postList() helpers

Flutter:
- shared/models: menu.dart, shopping_item.dart, diary_entry.dart
- features/menu: menu_service.dart, menu_provider.dart
  (MenuNotifier, ShoppingListNotifier, DiaryNotifier with family)
- MenuScreen: 7-day view, week nav, skeleton on generation,
  generate FAB with confirmation dialog
- ShoppingListScreen: items by category, optimistic checkbox toggle
- DiaryScreen: daily entries with swipe-to-delete, add-entry sheet
- Router: /menu/shopping-list and /menu/diary routes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 12:00:25 +02:00
dbastrikin
b9b9e9fe11 feat: implement Iteration 2 — product management
Backend:
- migrations/005: add pg_trgm extension + search indexes on ingredient_mappings
- migrations/006: products table with computed expires_at column
- ingredient: add Search method (aliases + ILIKE + trgm) + HTTP handler
- product: full package — model, repository (CRUD + BatchCreate + ListForPrompt), handler
- gemini: add AvailableProducts field to RecipeRequest, include in prompt
- recommendation: add ProductLister interface, load user products for personalised prompts
- server/main: wire ingredient and product handlers with new routes

Flutter:
- models: Product, IngredientMapping with json_serializable
- ProductService: getProducts, createProduct, updateProduct, deleteProduct, searchIngredients
- ProductsNotifier: create/update/delete with optimistic delete
- ProductsScreen: expiring-soon section, normal section, swipe-to-delete, edit bottom sheet
- AddProductScreen: name field with 300ms debounce autocomplete, qty/unit/days fields
- app_router: /products/add route + Badge on Products nav tab showing expiring count
- MainShell converted to ConsumerWidget for badge reactivity

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-21 23:22:30 +02:00
dbastrikin
e57ff8e06c feat: implement Iteration 1 — AI recipe recommendations
Backend:
- Add Groq LLM client (llama-3.3-70b) for recipe generation with JSON
  retry strategy (retries only on parse errors, not API errors)
- Add Pexels client for parallel photo search per recipe
- Add saved_recipes table (migration 004) with JSONB fields
- Add GET /recommendations endpoint (profile-aware prompt building)
- Add POST/GET/GET{id}/DELETE /saved-recipes CRUD endpoints
- Wire gemini, pexels, recommendation, savedrecipe packages in main.go

Flutter:
- Add Recipe, SavedRecipe models with json_serializable
- Add RecipeService (getRecommendations, getSavedRecipes, save, delete)
- Add RecommendationsNotifier and SavedRecipesNotifier (Riverpod)
- Add RecommendationsScreen with skeleton loading and refresh FAB
- Add RecipeDetailScreen (SliverAppBar, nutrition tooltip, steps with timer)
- Add SavedRecipesScreen with Dismissible swipe-to-delete and empty state
- Update RecipesScreen to TabBar (Recommendations / Saved)
- Add /recipe-detail route outside ShellRoute (no bottom nav)
- Extend ApiClient with getList() and deleteVoid()

Project:
- Add CLAUDE.md with English-only rule for comments and commit messages

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-21 22:43:29 +02:00
dbastrikin
24219b611e feat: implement Iteration 0 foundation (backend + Flutter client)
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>
2026-02-20 13:14:58 +02:00