- security: add mealie_base_url / mealie_api_token vault keys and
write/read/deleteMealieCredentials() to CryptoManager; the pairing
persists only in the platform keystore (Keychain / EncryptedSharedPreferences /
DPAPI) and never enters controller state
- data: add MealieApiClient - testConnection probes GET /api/app/about with
GET /api/recipes?perPage=1 fallback (200 = connected); fetchAllRecipes lists
GET /api/recipes?perPage=-1 and enriches each summary with
GET /api/recipes/{slug}, mapping recipeIngredient (structured maps + strings),
recipeInstructions (section flattening), nutrition display strings, ISO-8601
prep/cook times, yield, image + source URLs; Mealie UUID reused as Recipe.id
- data: add AppDatabase.upsertRecipe (insertOnConflictUpdate) and
DriftRecipeRepository.syncFromMealie preserving createdAt on re-sync
- presentation: add MealieIntegrationController (async credential load,
connectivity-gated Test & Save, Sync Now with spinner state, Disconnect,
isMealieRecipe badge/filter helper) plus api/sync providers
- settings: add Private Recipe Integrations card - Connect Mealie modal with
server URL hint, obscured API token field, Test & Save before persistence;
connected state shows endpoint, Sync Now and Disconnect
- recipes: add All / Web Scraped / Mealie source filter chips, subtle Mealie
badge on synced cards, and AppBar Sync Private Recipes action when configured
- fix _SettingsCard title Row overflow at 390dp (Expanded wrap) caught by the
mobile navigation widget test; add mealie_parser_test.dart (14 tests), suite
green at 184/184, flutter analyze clean
- docs: add architecture 4.9 Mealie design and multiplatform UX adaptation
spec; BUILD_STATE marks Phase 4B complete
Implements the architecture 4.8 resolution ladder: local SQLCipher cache ->
Open Food Facts (4s timeout) -> USDA FoodData Central fallback, with every
remote hit written through to the new FoodProductsTable (schema v5).
- domain: FoodProduct entity + IFoodSearchRepository contract (pure Dart)
- data: UsdaFdcClient (FDC nutrient-ID mapper 1008/1003/1004/1005/1079/1093,
api_key sent as query parameter - FDC ignores body keys and 403s otherwise),
OpenFoodFactsClient (userAgent with app identity, SearchTerms search,
getProductV3 barcode lookup), shared RemoteFoodDataSource interface
- presentation: FoodSearchController (400ms debounce, request-token
supersession), FoodSearchScreen (MobileScanner sheet on mobile, manual UPC
dialog + notification on desktop/web, macro action sheet with serving
multipliers, Log to Diary / Add to Pantry actions)
- integration: /food-search route with ?preset=pantry, Nutrition screen entry
button, Pantry screen Scan Item action
- hardening: FoodSearchUnavailableException distinguishes total source outage
from a healthy empty result; GoogleHealthConnector dotenv reads now guarded
with isInitialized (fixes NotInitializedError crashing app_navigation_test)
- tests: +16 (fallback sequence cache->OFF->USDA for text and barcode,
USDA request shape via MockClient); full suite 170/170, analyze clean
Domain (pure Dart):
- Add PantryItem and ShoppingListItem entities plus the PantryCategory aisle
enum (produce/meat_seafood/dairy/dry_goods/spices/other) with a keyword
infer() heuristic and ExpirationStatus warning buckets (expired /
expiringSoon within 3 days)
- Add measurement_units value object (mass/volume/count dimensions with
lb/oz/g/kg and ml/l/tsp/tbsp/cup conversion onto a shared base scale) and
ingredient_name canonicalizer (lowercase, parenthetical stripping,
plural folding: eggs -> egg, lbs -> lb)
- Define IPantryRepository contract covering inventory and checklist CRUD
- Add CalculateShoppingDelta use case: aggregates the active 7-day meal-plan
slot window's recipe ingredient manifests by canonical name, computes
deficit = totalRequired - currentPantryQuantity in base units (dominant
recipe unit wins display, 2-decimal rounding), upserts isAutoGenerated
rows with stable ids and source recipe titles, clears stale auto rows once
stock covers the plan, and never modifies or removes manual rows
Data:
- Bump AppDatabase to schema v4 (migration for v1-v3 installs) with
PantryItemsTable (UUID PK, canonical name, quantity/unit, aisle key,
nullable expirationDate, lastUpdated) and ShoppingListItemsTable (UUID PK,
required quantity/unit, purchased/auto flags, JSON sourceNotes for recipe
titles) plus watch/get/upsert/delete helpers
- Implement DriftPantryRepository (record <-> entity mapping, enum keys,
JSON recipe-title round-trip)
Presentation:
- Add PantryInventoryController (live watch stream, quick +/- stock steps
clamped at zero, aisle-grouping vs expiration-sort modes) and
ShoppingListController (manual add, All/To Buy/Checked filter, check-off
with "Add to Pantry?" unit-converting stock merge or aisle-inferred row
creation, syncWithMealPlan reading the planner's Monday-Sunday window and
recipe catalog)
- Refactor PantryScreen: Inventory/Shopping tab header with remaining-count
badge, Aisles/Expiration segmented control, categorized tiles with +/-
counters and expired/expiring badges, swipe-to-delete on both lists,
prominent "Sync with Meal Plan" banner with run summary + snackbar,
"From: <recipe>" origin chips on auto rows, checklist filters, check-off
-> pantry prompt, and Add Pantry Item / Add Shopping Item dialogs
Testing:
- 10 new tests in delta_calculator_test.dart: 300 g on hand vs 800 g
requirement -> +500 g entry, sufficient stock -> no entry, manual rows
preserved across repeated syncs, stale auto cleanup, cross-recipe
plural/unit normalization (4 eggs + 2 egg - 2 -> +4 egg), lb/oz scale
unification (0.75 lb), check-off -> pantry transfer (merge +
aisle-inferred create), end-to-end syncWithMealPlan, and inventory
canonicalization / quick-adjust clamp; full suite green (99/99)
- flutter analyze clean (No issues found!) after codegen; BUILD_STATE.md:
mark Phase 5 complete and record all verification gates
Domain (pure Dart):
- Add Recipe, MacroProfile, and RecipeIngredient entities (quantity/unit/
item parsing: decimals, ASCII/unicode/mixed fractions, known-unit
vocabulary) plus MealPlanSlot reusing the nutrition MealType enum
- Define IRecipeRepository and IMealPlanRepository contracts
- Add ScrapeRecipeFromUrl use case: package:http download with injectable
fetcher, package:html DOM parse, JSON-LD @graph/root/array/list-@type
navigation, HowToStep/HowToSection flattening, ISO-8601 prep/cook times,
recipeYield, regex nutrition parsing, OpenGraph fallback, and typed
RecipeScrapeException
- Add core/utils/uuid.dart RFC-4122 v4 generator (no new dependency)
Data:
- Bump AppDatabase to schema v3 (migration for v1/v2 installs) with
RecipesTable (UUID PK, JSON ingredient/instruction columns, macros) and
MealPlanSlotsTable (UUID PK, nullable recipeId FK ON DELETE SET NULL)
- Implement DriftRecipeRepository (URL-deduplicated importRecipe, createdAt
preserved on re-import) and DriftMealPlanRepository (half-open [from, to)
window, upsert by id)
Presentation:
- Add RecipeImportController (URL submit -> loading -> preview modal state,
live catalog watch stream, confirm-save/dismiss/delete) and
MealPlanCalendarController (Monday-Sunday window navigation, slot
assign/upsert/clear, prepared toggles, per-day protein rollup against the
100 g target, deleted-recipe slot normalization)
- Rework MealPlannerScreen: URL import bar with spinner, RecipePreviewDialog
(macros, ingredients, steps, save with error surface), draggable
SavedRecipesTray with protein badges, responsive WeeklyCalendarView
(>=900 dp: 7-column grid, below: tabbed pager) with DragTarget slots and
daily protein indicators, AssignSlotDialog quick-assign modal
Fixes:
- Eliminate desktop resize overflow (RenderFlex hatched stripes): wide-mode
day columns scroll vertically, and a LayoutBuilder collapses the page
into a single scrollable column with a fixed-height calendar when the
window body is shorter than ~520 dp
Testing:
- 29 new tests: recipe scraper (JSON-LD variants, macro/ingredient/
instruction/duration parsing, OG fallback, execute error mapping),
meal-plan repositories (window filter, upsert, dedup), calendar
controller (rollup, navigation, prepared/custom entries), and screen
widget tests pumping short-compact/short-wide/tall/extremely-short
window sizes to prove no overflow; full suite green (89/89)
- BUILD_STATE.md: mark Phase 4 complete and record all verification gates
Domain (pure Dart):
- Add 12-quadrant InjectionSite anatomical model with a least-recently-used
rotation policy, DoseEntry entity, and Glp1Compound value object
(Semaglutide 168h, Tirzepatide 120h, Liraglutide 13h)
- Add CalculateSerumLevel use case implementing
C(t) = sum(D_i * 0.5^((t - t_i) / t_half))
- Define IMedicationRepository contract; retire superseded MedicationDose
Data:
- Add injection-history CRUD to AppDatabase (watch/get/insert/update/delete/
latest-administered), ordered newest first
- Implement drift-backed MedicationRepository with record-to-entity mapping
Presentation and local alerts:
- Add MedicationController (StateNotifier) managing serum estimation, site
rotation suggestions, and best-effort reminder scheduling
- Rework MedicationScreen: serum card, suggested-site card, interactive log
dialog (compound, dosage, timestamp, quadrant), history with delete, and a
60s decay ticker
- Add INotificationService / PlatformNotificationService wrapping
flutter_local_notifications (one-shot scheduling on Android/iOS/macOS,
graceful no-op on Web/Windows/Linux), Android boot receivers for reminder
restoration, and the timezone dependency
Development tooling:
- Add debug-only "Continue Without Signing In" bypass on the login screen,
backed by an in-memory devAuthBypassProvider; the router guard honors the
flag and the shell shows a dev-mode banner while active
Fixes:
- Resolve the Supabase client lazily in SupabaseAuthService so widget tests
and pre-initialization builds no longer throw
Testing:
- 36 new tests covering PK decay math, rotation determinism, repository CRUD
on an in-memory drift database, controller behavior with fakes, and the
end-to-end dev bypass flow; full suite green (37/37)
- BUILD_STATE.md: mark Phase 2 complete and record all verification gates