21 KiB
21 KiB
Build Execution Ledger
Active Target
- Current Milestone: Phase 7: Navigation & Domain Expansion (Exercise, Recipe Vault, Settings) — COMPLETE
- Status: Phase 7 Complete (release-ready: all phases 1–7 done)
Phase Checklist
- Phase 1: Foundation & Identity (Passwordless SSO & Encrypted Drift DB)
- Phase 2: Medication Domain, Injection Rotation, Serum Modeling
- Phase 3: Nutrition, Protein Target Tracking, HealthGateway Sync
- Phase 4: Recipe Web Scraper Engine & Meal Planning Calendar
- Phase 5: Pantry Delta Engine & Auto-Shopping Generator
- Phase 6: Hardening, Native Desktop/Web Polish, CI/CD
- Phase 7: Navigation & Domain Expansion (Exercise, Recipe Vault, Settings)
Phase 2 Deliverables (2026-10-16)
- Domain:
DoseEntryentity, 12-quadrantInjectionSiterotation model,Glp1Compoundvalue object (Sema 168h / Tirze 120h / Lira 13h),IMedicationRepositorycontract. - Domain:
CalculateSerumLeveluse case implementingC(t) = Σ D_i · 0.5^((t − t_i)/t_half). - Data: injection-history CRUD on
AppDatabase+ drift-backedMedicationRepository(record ↔ entity mapping). - Presentation:
MedicationController(Riverpod StateNotifier) with serum estimation, least-recently-used site rotation, and reminder scheduling; refactoredMedicationScreen(serum card, rotation suggestion, interactive log dialog, history with delete). - Platform:
INotificationService/PlatformNotificationService(flutter_local_notifications 17.x; Android/iOS/macOS scheduled reminders, graceful no-op on Web/Windows/Linux); Android boot-receiver manifest entries;timezonedependency added. - Regression fix:
SupabaseAuthServicenow resolves its client lazily (baseline widget test no longer throws when Supabase is uninitialized). - Tests: 33 new unit/widget-fake tests (PK calculator, rotation policy, repository w/ in-memory drift, controller); baseline smoke test green.
Post-Phase-2 Additions
- Development auth bypass (2026-10-16): debug-only "Continue Without Signing In" button on the login screen (
kDebugMode-gated, in-memorydevAuthBypassProvider) lets reviewers enter the app without a Supabase session; the router guard honors the flag and the shell shows an orange "Development mode - not signed in" banner while active. Covered bytest/features/auth/dev_auth_bypass_test.dart(3 tests).
Phase 3 Deliverables (2026-10-16)
- Domain:
FoodLogEntry(+MealTypeenum),HydrationLogEntry,BiometricEntry(+BiometricSourceenum),INutritionRepository,IHealthSyncRepository— all pure Dart. - Data:
AppDatabaseschema v2 (+ migration for v1 installs) withFoodLogsTable,HydrationLogsTable,BiometricLogsTable, range-filtered watch/get/CRUD helpers,clearSyncedRangefor idempotent re-syncs; drift-backedNutritionRepositoryandHealthSyncRepository(record ↔ entity mapping). - Platform:
HealthGatewayrewrite —IHealthGateway+ conditional-import factory; mobile impl (health_gateway_mobile.dart) wrapspackage:healthforWEIGHT(kg→lb normalization) andACTIVE_ENERGY_BURNED(joule→kcal) with double-guarded try/catch; fallback impl (health_gateway_fallback.dart) for Web/Windows/macOS never touches platform channels (reads served from the drift cache, writes via manual entry). - Presentation:
NutritionController(RiverpodAsyncNotifier; today's logs, 100 g protein / 96 oz water targets, meal grouping, quick-add water) andBiometricController(weight history, 7/30-day deltas, manual entry, native sync with per-day active-energy aggregation);LogFoodDialogandLogWeightDialog. - Presentation:
NutritionScreenupgraded — interactive protein/hydration progress meters, +8/+16/+24 oz quick-add buttons, meal-grouped food diary with delete, weight tracker card (latest weight, 7/30-day trend chips, latest active burn) whose "Log Weight / Sync" button runsHealthGatewaysync on Android/iOS and opens the manual dialog on Windows/Web; status snackbars viaref.listen. - Tests: 23 new tests — nutrition repository (in-memory drift: CRUD, day-range filter, watch,
MealTyperound-trip), nutrition controller (targets, grouping, validation, quick-add, delete, failure retention), biometric controller (manual entry, deltas, fallback short-circuit, permission denial, weight+energy sync idempotency). - Regression fix: none required — existing 37 tests untouched and green.
Phase 4 Deliverables (2026-10-16)
- Domain:
Recipe(+MacroProfile),RecipeIngredient(quantity/unit/item parsing: decimals, ASCII + unicode + mixed fractions, known-unit vocabulary),MealPlanSlot(reuses nutritionMealType),IRecipeRepository,IMealPlanRepository— all pure Dart;core/utils/uuid.dartv4 generator (no new dependency). - Domain:
ScrapeRecipeFromUrluse case —package:httpdownload (injectableRecipeHtmlFetcherfor tests),package:htmlDOM parse, JSON-LD@graph/root/array/@type-list navigation,HowToStep/HowToSectioninstruction flattening, ISO-8601prepTime/cookTime,recipeYield, regex nutrition parsing (calories,proteinContent,carbohydrateContent,fatContent), image list/object unwrap, OpenGraph fallback (og:title/og:image/og:description), typedRecipeScrapeException. - Data:
AppDatabaseschema v3 (migration for v1/v2 installs) withRecipesTable(UUID PK, JSON ingredient/instruction columns, macros,createdAt) andMealPlanSlotsTable(UUID PK, plannedDate, mealType, nullablerecipeIdFK withON DELETE SET NULL, customNotes, isCompleted); drift-backedDriftRecipeRepository(URL-deduplicatedimportRecipe,createdAtpreserved on re-import) andDriftMealPlanRepository(half-open[from, to)window, upsert-by-id). - Presentation:
RecipeImportController(RiverpodNotifier; URL submit → loading → preview modal state, live catalog watch stream, confirm-save/dismiss/delete) andMealPlanCalendarController(Monday–Sunday window, next/previous/today navigation, slot assign/upsert/clear, prepared toggle, per-day protein rollup against the nutrition 100 g target, deleted-recipe slot normalization, stale-window refresh guard). - Presentation:
MealPlannerScreenrefactor — URL import bar with spinner,RecipePreviewDialog(image, meta chips, macro tiles, ingredients, steps, save with error surface),SavedRecipesTray(draggable cards with thumbnail + protein badge + quick-assign),WeeklyCalendarView(≥900 dp: 7-column grid; below: tabbed pager) withDragTargetmeal slots, prepared checkboxes, clear menu, per-day protein indicator (progress bar vs. target),AssignSlotDialog(saved-recipe dropdown or custom description), week navigation header. - Tests: 25 new tests —
recipe_scraper_test.dart(JSON-LD@graph/root/array/list-@typeextraction, macro/ingredient/instruction/duration/yield parsing, malformed-JSON fallthrough, OpenGraph fallback,execute()URL validation & error mapping) andmeal_plan_repository_test.dart(startOfWeek, recipe CRUD/URL-dedup/watch, slot window filter/upsert/delete, calendar rollup/assignment/navigation/custom entries/prepared toggles/recipe-deletion normalization, import-controller empty-URL guard). - Regression fix: HowToSection flattening now recurses into
itemListElementbefore using the sectionname(parser ordering bug caught by the new test).
Post-Phase-4 Additions
- Desktop resize overflow fix (2026-10-16): the hatched yellow/black stripes (RenderFlex overflow) shown when shrinking the Windows window are gone — wide-mode day columns now scroll vertically (matching the compact pager), and a
LayoutBuilderinMealPlannerScreencollapses the page into a single scrollable column (with a fixed-height calendar) whenever the body is shorter than ~520 dp. Covered bytest/features/meal_planner/meal_planner_screen_test.dart(4 widget tests pumping the screen at short-compact, short-wide, tall, and extremely-short window sizes; overflow errors fail widget tests, so green proves no stripes).
Phase 5 Deliverables (2026-10-16)
- Domain:
PantryItem(+PantryCategoryaisle enum withinfer()keyword heuristic,ExpirationStatuswarning buckets),ShoppingListItem,IPantryRepository, and two pure-Dart value objects —ingredient_name.dart(lowercase, paren-stripped, plural-folded canonical keys:eggs→egg) andmeasurement_units.dart(mass/volume/count dimensions with lb↔oz↔g↔kg and ml/l/tsp/tbsp/cup conversion). - Domain:
CalculateShoppingDeltause case — aggregates the active 7-dayMealPlanSlotwindow'sRecipeingredient manifests by canonical name, appliesdeficit = totalRequired − currentPantryQuantityin dimension base units (dominant recipe unit wins display, 2-decimal rounding), upsertsisAutoGeneratedrows withsourceRecipeTitles(stable ids, checked-state kept only while the requirement is unchanged), clears stale auto rows (satisfied by stock or superseded by a manual row), and never modifies or removes manual rows. - Data:
AppDatabaseschema v4 (migration for v1–v3 installs) withPantryItemsTable(UUID PK, canonical name, quantity/unit, aisle key, nullableexpirationDate,lastUpdated) andShoppingListItemsTable(UUID PK, required quantity/unit, purchased/auto flags, JSONsourceNotesfor recipe titles); drift-backedDriftPantryRepository(watch/get/upsert/delete for both lists, enum/JSON mapping). - Presentation:
PantryInventoryController(live watch stream, quick ± stock steps clamped at zero, aisle-grouping vs. expiration-sort modes) andShoppingListController(manual add, All/To Buy/Checked filter, check-off with "Add to Pantry?" prompt performing a unit-converting stock merge or aisle-inferred row creation,syncWithMealPlanreading the planner's Monday–Sunday window + recipe catalog). - Presentation:
PantryScreenrefactor — 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, auto-row "From: " origin chips, filter segments, check-off → pantry prompt, and Add Pantry Item / Add Shopping Item dialogs. - Tests: 10 new tests in
test/features/pantry/delta_calculator_test.dart— the three required scenarios (300 g vs 800 g → +500 g entry; sufficient stock → no entry; manual rows preserved across repeated syncs) plus 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-endsyncWithMealPlan, and inventory canonicalization/quick-adjust clamp.
Phase 6 Deliverables (2026-10-16)
- Platform:
HealthGatewayhardened —IHealthGatewayfactory now accepts anILocalBiometricReader(core contract); mobile driver guards withkIsWeb+defaultTargetPlatform+dart:iocross-check, probes Health Connect SDK availability (getHealthConnectSdkStatus) and degrades with a debug warning when missing/disabled, validatesREAD_WEIGHT/READ_STEPS/READ_TOTAL_CALORIES_BURNED/READ_ACTIVE_CALORIES_BURNEDpermissions viahasPermissions/requestAuthorizationbefore every read, and keeps all HealthKit entitlement failures insidetry/catch(false + warning, never an unhandled exception). - Platform: fallback driver (
health_gateway_fallback.dart) now serves locally logged biometrics through an injectedILocalBiometricReader(wired to the drift cache byDriftLocalBiometricReaderin the health_sync controller) and emits a debug warning instead of attempting native SDK calls; Android manifest declares the four Health ConnectREAD_*permissions matching the gateway request set. - Security:
CryptoManagerplatform-safe vault config — AndroidAndroidOptions(encryptedSharedPreferences: true), iOS/macOSKeychainAccessibility.first_unlock(Darwin Keychain options), Windows DPAPI-sealed file, Linux libsecret; vault failures degrade to an ephemeral in-memory SQLCipher key instead of crashing. - Testability:
isAuthenticatedProviderextracted inapp_router.dartso integration tests can simulate authenticated/unauthenticated sessions without booting Supabase. - Tests:
test/features/medication/serum_calculator_test.dart(11 tests — semaglutide 168 h / tirzepatide 120 h weekly steady-state peaks/troughs vs. closed-form geometric series, monotonic accumulation, peak-to-trough fluctuation ratios, multi-dose superposition, brand-alias resolution, titration ladder, washout). - Tests:
recipe_scraper_test.dartextended with real-world fixtures (test/fixtures/recipes/bbc_good_food_style.htmlroot-level payload,allrecipes_style.htmlmulti-script complex@graphwithHowToSectionnesting, list-valued yield/image) — 2 new fixture tests. - Tests:
delta_calculator_test.dartextended with a unit-conversion group (kg+g dominant-unit reporting, l+ml volume unification, mass/volume dimension-mismatch guard, unit-synonym/plural canonicalization with cross-dimension stock refusal) — 4 new tests. - Tests:
test/integration/app_navigation_test.dart(4 tests — navigation-rail switching across/medication,/nutrition,/meal-planner,/pantryunder authenticated and dev-bypass conditions, mobile bottom-bar switching, unauthenticated bounce to/login) andtest/features/nutrition/nutrition_widget_test.dart(3 tests — protein/hydrationLinearProgressIndicatorvalues and labels updating reactively on food/water logs, goal-met chips). - CI/CD:
.forgejo/workflows/ci.ymlforgit.kellsupport.com/mtkell/glp-companion(GitHub Actions-compatible syntax for Forgejo Actions) — push tomain+ all PRs inghcr.io/cirruslabs/flutter:stable; checkout → mock.env→flutter pub get→dart run build_runner build --delete-conflicting-outputs→flutter analyze --fatal-infos→flutter test --coverage→flutter build bundle+flutter build web. - Cleanup: dead
lib/features/recipes/data/recipe_scraper.dart(unreferenced Phase-0 scaffold superseded byScrapeRecipeFromUrl) removed with its empty directory; zero TODO/FIXME markers inlib/ortest/. - Docs:
README.mdrewritten (architectural overview + directory layout, local dev prerequisites incl. VS Code/Cline setup and.envconfiguration via new.env.example, SQLCipher AES-256 key lifecycle, CI pipeline details);BUILD_STATE.mdPhase 1–6 checkboxes complete.
Phase 7 Deliverables (2026-10-16)
- Domain:
MuscleGroupenum +ExerciseEntryentity (pure Dart),IExerciseRepositorycontract, session-scopedInMemoryExerciseRepository(drift table deferred to a follow-up migration). - Presentation (exercise):
ExerciseController(weekly distinct-session rollup vs. the recommended 3 sessions/week lean-mass target) +ExerciseScreenwith weekly strength-frequency card, day-grouped working-set log (swipe-to-delete), and theLogExerciseDialog(muscle group / name / sets / reps / weight). - Presentation (Recipe Vault): new
features/recipesscreen — pinned URL import bar wired toRecipeImportController, search field + macroChoiceChipfilters ("Protein > 30g", "Protein > 20g", "Under 500 kcal", All), responsiveGridView.builder(SliverGridDelegateWithMaxCrossAxisExtent) with chunked infinite scroll (24-card pages, 6-card prefetch margin), details bottom sheet, delete confirmation; reuses the meal-planner recipe domain (no duplicate bounded context). - Refactor (meal planner):
MealPlannerScreenstripped to week navigation + 7-day calendar + daily protein rollups; inline import bar andSavedRecipesTrayremoved (moved to Recipe Vault);AssignSlotDialog/drag-drop replaced by the searchableAssignRecipeSheetmodal bottom sheet (Recipe Vault search + custom-entry fallback); empty slots render a "+ Assign Recipe" affordance. - Presentation (Settings):
SettingsScreen— Identity (email, Supabase status badge, sign-out), Targets (daily protein g / water oz inputs feedingNutritionController.updateTargets), Security & Storage (SQLCipher AES-256 badge with web-degradation note), Integrations (Apple Health / Health Connect / HealthGateway toggles viaIntegrationsController, native permission round-trips throughIHealthGateway). - Shell:
app_router.dartregisters/medication,/nutrition,/exercise,/meal-planner,/recipes,/pantry,/settings;AdaptiveHomeScaffolddesktop rail expanded to the 7 specified destinations (medication_liquid,restaurant,fitness_center,calendar_month,menu_book,inventory_2,settings) withNavigationRailLabelType.allinside a height-constrained scroll view (no compact-window overflow); mobile keeps 4 core tabs + a "More" tab opening the secondary-destinations drawer (Exercise, Recipes, Settings). - Tests:
app_navigation_test.dartextended to all 7 rail routes + mobile "More" drawer flow (7 tests total);meal_planner_screen_test.dartrewritten for the calendar-only layout including the assign-sheet opening;SettingsScreenbadge rows useFlexible+ ellipsis so narrow widths never overflow.
Post-Phase-7 Additions
- Recipe import hardening (2026-10-16): in-browser URL imports failed with "could not download" — diagnosed against the live allrecipes recipe: the native fetch succeeds (HTTP 200) and the parser extracts the recipe correctly, so the failure is the web CORS policy (recipe sites never send
Access-Control-Allow-Origin, so Chrome aborts before a status code exists); additionally, requests without a browser-likeUser-Agentdraw HTTP 403 from anti-bot gates. Fixes inScrapeRecipeFromUrl: (1) browser-likeAccept/Accept-Language/User-Agentheaders on native (UA stripped on web — forbidden header); (2) optional--dart-define=RECIPE_PROXY_URL=<url>that routes downloads through a server-side proxy as<proxy>?url=<encoded>; (3) actionable CORS guidance in the web error message when no proxy is configured. Shippedsupabase/functions/recipe-proxy/— a deployable Deno Edge Function relay (http(s)-only, SSRF host guard, 2 MiB cap, 15 s timeout, CORS-enabled) — deploy withsupabase functions deploy recipe-proxyand pass its URL via the define. Tests: 7 new cases inrecipe_scraper_test.dart(header maps, proxy wrapping/encoding, error propagation) — 22/22 scraper tests green. - Web drift assets fix (2026-10-16): every drift operation failed at runtime on
flutter run -d chrome(watchDoseEntries failed,meal plan refresh failed,recipe catalog watch failed, …) becauseweb/was missing the WASM database artifactsconnection/web.dartdocuments as required. Addedweb/sqlite3.wasm(740 KB, downloaded from thesqlite3-3.7.1GitHub release — matches the lockedsqlite3version; drift 2.35.x changelog mandates a 3.x wasm) andweb/drift_worker.js(copied from the lockeddrift-2.35.2package). Both files are not gitignored, so they ship with the repo and the CIflutter build webstep. Verified:flutter build web --no-tree-shake-iconspackages both intobuild/web/. NOTE: bump both files when upgradingdrift/sqlite3(worker JS must match the Dart package version exactly; wasm must be ≥ 3.x). - Android toolchain fixes (2026-10-16):
flutter runon an emulator failed on three pre-existing gaps (Android builds had never been exercised — Phase 6 gates ran on Windows/Web): (1)flutter_local_notificationsrequires core library desugaring → enabledisCoreLibraryDesugaringEnabled+com.android.tools:desugar_jdk_libs:2.1.4inandroid/app/build.gradle.kts; (2) Kotlin incremental compilation aborts when the pub cache lives on a different drive than the project (C: vs D:, "this and base files have different roots") →kotlin.incremental=falseinandroid/gradle.properties; (3) the pinnedhealthplugin declares minSdk 26 vs. Flutter's default 24 →minSdk = maxOf(flutter.minSdkVersion, 26)(Health Connect only ships on Android 8.0+). Verified:flutter build apk --debug→✓ Built build\app\outputs\flutter-apk\app-debug.apk. - Mobile overflow fix (2026-10-16): the compound dropdown in
LogInjectionDialogpainted yellow/black stripes on phone widths —DropdownButtonFormField's selected-item Row cannot ellipsize long labels ("Tirzepatide (Mounjaro / Zepbound)") withoutisExpanded: true. Fixed withisExpanded: true+maxLines: 1/TextOverflow.ellipsison the menu items; regression tests intest/features/medication/log_injection_dialog_test.dartpump the dialog at 320 dp (both long labels) and 390 dp asserting no RenderFlex overflow (128/128 tests passing).
Active Verification Gates (Phase 7, 2026-10-16)
dart analyze lib test(Zero issues)flutter test(all passing — 125/125)flutter run -d windows— builds cleanly; all 7 rail destinations switch without overflow (integration tests assert this headlessly)
Phase 6 Verification Gates (2026-10-16, retained)
dart run build_runner build --delete-conflicting-outputs(44 outputs written)flutter analyze --fatal-infos(Zero issues —No issues found!)flutter test(123/123 passing)flutter build web --no-tree-shake-icons(conditional import keepspackage:health/dart:ioout of the web bundle)