- 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
- Exercise feature (lean-mass preservation): MuscleGroup enum + ExerciseEntry
entity, IExerciseRepository contract with session-scoped in-memory
implementation, ExerciseController with weekly distinct-session rollup vs.
the recommended 3 sessions/week target, ExerciseScreen (weekly strength
frequency card, day-grouped working-set log with swipe-to-delete) and the
LogExerciseDialog (muscle group / name / sets / reps / weight).
- Recipe Vault: new features/recipes screen with pinned URL import bar wired
to RecipeImportController, search field + macro filter chips (Protein >
30g / > 20g / Under 500 kcal / All), responsive GridView.builder
(SliverGridDelegateWithMaxCrossAxisExtent) with chunked infinite scroll,
read-only details bottom sheet, and delete confirmation.
- Meal planner refactor: strip the screen to week navigation + the 7-day
calendar (Mon-Sun) with daily protein rollups; replace the inline import
bar, SavedRecipesTray, and AssignSlotDialog with the searchable
AssignRecipeSheet modal bottom sheet (Recipe Vault search + custom-entry
fallback); empty slots render a "+ Assign Recipe" affordance; drop the
drag-and-drop tray plumbing.
- Settings: new features/settings screen with Identity (email, Supabase
status badge, sign-out), Targets (daily protein/water inputs feeding the
new NutritionController.updateTargets), Security & Storage (SQLCipher
AES-256 badge with web-degradation note), and Integrations (Apple Health /
Health Connect / HealthGateway toggles via IntegrationsController through
the IHealthGateway boundary).
- Shell: register /exercise, /recipes, /settings routes; expand the desktop
NavigationRail to the 7 specified destinations (medication_liquid,
restaurant, fitness_center, calendar_month, menu_book, inventory_2,
settings) with labelType.all inside a height-constrained scroll view;
mobile keeps 4 core bottom-bar tabs plus a "More" tab opening the
secondary-destinations drawer.
- Android toolchain fixes (first-ever Android build): enable core library
desugaring + desugar_jdk_libs 2.1.4 for flutter_local_notifications, set
kotlin.incremental=false (pub cache on C: vs project on D: breaks the
incremental compiler), and raise minSdk to maxOf(flutter, 26) for the
health plugin.
- Mobile overflow fix: LogInjectionDialog compound dropdown gains
isExpanded + ellipsis so long labels ("Tirzepatide (Mounjaro /
Zepbound)") no longer paint stripes at phone widths.
- Web drift assets: commit web/sqlite3.wasm (sqlite3-3.7.1 release, matching
the locked package) and web/drift_worker.js (drift-2.35.2) so the WASM
database opens at runtime; both were missing and every drift operation
failed in Chrome.
- Recipe import hardening: browser-like Accept/Accept-Language/User-Agent
headers on native (403 anti-bot bypass; UA stripped on web), optional
--dart-define=RECIPE_PROXY_URL routing downloads through a server-side
proxy, actionable CORS guidance in the web error path, and a deployable
Supabase Edge Function relay (supabase/functions/recipe-proxy) with
http(s)-only validation, SSRF host guard, 2 MiB cap, and 15 s timeout.
- Tests: 123 -> 135 (exercise controller/screen, log-injection dialog
320dp/390dp overflow regressions, scraper header/proxy/error-path
hardening, meal planner calendar-only rewrite); navigation integration
tests now cover all 7 rail routes and the mobile More drawer.
- Docs: README web recipe-import (CORS proxy) section; BUILD_STATE.md
Phase 7 deliverables, Post-Phase-7 additions, and checklist updated.
Verified: dart analyze lib test clean, flutter test 135/135,
flutter build windows --debug, flutter build apk --debug, and
flutter build web --no-tree-shake-icons all succeed.
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