- Harden HealthGateway: kIsWeb/defaultTargetPlatform/dart:io guards, Health Connect SDK availability probe with graceful degradation, READ_WEIGHT/ READ_STEPS/READ_TOTAL_CALORIES_BURNED permission validation before reads, HealthKit entitlement failures contained (false + debug warning), and a desktop/web fallback that serves locally logged biometrics through the new ILocalBiometricReader contract (drift-backed adapter wired in health_sync). - Platform-safe CryptoManager vault: EncryptedSharedPreferences (Android), Keychain first_unlock (iOS/macOS), DPAPI-sealed file (Windows), libsecret (Linux); vault failures degrade to an ephemeral in-memory SQLCipher key. - Declare the four Health Connect READ_* permissions in AndroidManifest.xml. - Extract isAuthenticatedProvider so integration tests can simulate auth sessions without booting Supabase. - Add 24 tests (99 -> 123): steady-state PK suite (Semaglutide 168h / Tirzepatide 120h geometric series, superposition, titration, washout), real-world recipe JSON-LD fixtures (BBC-style root, AllRecipes @graph), pantry unit-conversion group (kg/g, l/ml, dimension guard, synonyms), app navigation integration tests (rail + bar, authenticated + dev-bypass + unauthenticated), and reactive nutrition progress-bar widget tests. - Fix NutritionScreen narrow-width overflows: Flexible protein title and Wrap action buttons stack cleanly on compact phones. - Add .forgejo/workflows/ci.yml for git.kellsupport.com/mtkell/glp-companion (push to main + PRs, Flutter container: checkout, mock .env, pub get, build_runner, analyze --fatal-infos, test --coverage, build bundle + web). - Remove dead lib/features/recipes/data/recipe_scraper.dart; rewrite README.md (architecture, dev setup, SQLCipher details, CI docs); add .env.example; allow it through .gitignore; update BUILD_STATE.md with Phase 6 deliverables and complete Phases 1-6 checklist. Verified: build_runner 44 outputs, flutter analyze --fatal-infos clean, flutter test 123/123, flutter build web and flutter build bundle succeed. |
||
|---|---|---|
| .forgejo/workflows | ||
| android | ||
| docs | ||
| ios | ||
| lib | ||
| macos | ||
| test | ||
| web | ||
| windows | ||
| .clinerules | ||
| .env.example | ||
| .gitignore | ||
| .metadata | ||
| BUILD_STATE.md | ||
| README.md | ||
| analysis_options.yaml | ||
| pubspec.lock | ||
| pubspec.yaml | ||
README.md
GLP-1 Companion
Cross-platform GLP-1 medication, nutrition, and pantry tracking companion for people on Semaglutide (Ozempic/Wegovy), Tirzepatide (Mounjaro/Zepbound), and Liraglutide (Saxenda/Victoza) therapy.
Targets: Android · iOS · Web · macOS · Windows Stack: Flutter 3.x / Dart 3.x · Riverpod · GoRouter · Drift (SQLCipher) · Supabase Auth
Feature highlights
- Pharmacokinetics & dose logistics — single-compartment serum-level
modeling (
C(t) = Σ D_i · 0.5^((t−t_i)/t_half)) with per-compound half-lives (Sema 168 h / Tirze 120 h / Lira 13 h), titration logging, a 12-quadrant injection-site rotation model, and offline dose reminders. - Lean-mass preservation — daily protein (100 g) and hydration (96 oz) targets with reactive progress meters and a weight-trend tracker.
- Recipe scraping & meal planning — schema.org/JSON-LD recipe importer
(
@graph,HowToSection, ISO-8601 durations, OpenGraph fallback), weekly meal-plan calendar, and portion-aware macro rollups. - Pantry delta engine — canonical ingredient keys, dimension-aware unit conversions (mass/volume/count), and one-tap "Sync with Meal Plan" that generates, refreshes, and clears a shopping list while preserving manual rows.
- Decoupled biometric ingestion —
HealthGatewaywraps Health Connect (Android) and HealthKit (iOS) behind a platform guard; Windows/macOS/Web degrade to manual entry served from the encrypted local cache. - High-privacy persistence — SQLCipher AES-256 encrypted drift database with an OS-vaulted master key; passwordless Supabase auth (magic links, OTP) plus Google/Apple/Microsoft OAuth.
Architecture
Feature-first Clean Architecture — every bounded context owns its domain,
data, and presentation layers, and core/ never imports feature code:
lib/
├── app/ # Bootstrap, GoRouter routes + auth guard, adaptive scaffold
├── core/ # Shared foundation (no feature imports)
│ ├── database/ # Drift schema (SQLCipher) + conditional web/native connections
│ ├── error/ # Failure models
│ ├── network/ # Supabase availability probe
│ ├── platform/ # HealthGateway, notification service (OS adapters)
│ ├── security/ # CryptoManager (SQLCipher key vault)
│ ├── theme/ # Material 3 / Cupertino adaptive theming
│ └── utils/ # Date/time helpers, UUID generation
└── features/ # Modular bounded contexts
├── auth/ # Passwordless SSO, OAuth, debug sign-in bypass
├── health_sync/ # Biometric cache, trends, HealthGateway sync
├── meal_planner/ # JSON-LD scraper, recipe catalog, weekly calendar
├── medication/ # Doses, injection rotation, serum modeling
├── nutrition/ # Food/hydration logs, protein + hydration targets
└── pantry/ # Inventory, shopping delta engine, unit conversions
Each feature follows the same internal layout:
<feature>/
├── domain/ # Pure Dart: entities, value objects, repository
│ ├── entities/ # interfaces, use cases (ZERO framework imports)
│ ├── value_objects/
│ ├── repositories/
│ └── usecases/
├── data/ # Drift-backed repository implementations
└── presentation/ # Riverpod notifiers, screens, adaptive widgets
Platform-awareness rules:
- Presentation code never imports
dart:io-bound packages; OS APIs sit behind core contracts (IHealthGateway,INotificationService) selected by conditional imports andkIsWeb/defaultTargetPlatformguards. - Web/desktop builds gracefully degrade (manual entry, no-op reminders) instead of crashing on missing platform channels.
- Responsive layout: mobile (<600 dp) uses
NavigationBar, desktop/web (≥1024 dp) uses aNavigationRail; Apple targets lean Cupertino.
Getting started
Prerequisites
- Flutter SDK ≥ 3.16 (stable channel; Dart 3.x) —
flutter doctormust be clean for the platforms you intend to run. - VS Code + Cline (or another MCP-capable agent): the repository ships
.clineruleswith the architecture contract andBUILD_STATE.mdas the milestone ledger. Ask the agent to "read BUILD_STATE.md" first so it resumes at the correct phase. - A Supabase project (optional for local review): without it the app boots into the login screen; debug builds can tap Continue Without Signing In to explore with an in-memory session.
Environment configuration
pubspec.yaml bundles a .env asset, so the file must exist before
flutter run/flutter build:
cp .env.example .env # then fill in your project values
SUPABASE_URL=https://YOUR_PROJECT.supabase.co
SUPABASE_PUBLISHABLE_KEY=sb_publishable_...
Missing values are tolerated at runtime (the Supabase client stays uninitialized and the app falls back to the dev bypass), but the file itself must exist for asset bundling.
Run it
flutter pub get
dart run build_runner build --delete-conflicting-outputs # regenerate drift code
flutter run -d windows # or: chrome / macos / android / ios
Verify
flutter analyze --fatal-infos # zero tolerance lint gate
flutter test --coverage # unit + widget + integration suites
flutter build web --no-tree-shake-icons
Database encryption (SQLCipher AES-256)
All health markers (doses, weight, nutrition logs, pantry) live in a
Drift database compiled as SQLCipher through the sqlite3 build-hook
define in pubspec.yaml (hooks.user_defines.sqlite3.source: sqlcipher).
Key lifecycle (lib/core/security/crypto_manager.dart):
- A random 256-bit master key is generated on first launch with
Random.secure(). - The key is vaulted in the OS secret store with platform-hardened options:
- Android — Jetpack Crypto
EncryptedSharedPreferences(encryptedSharedPreferences: true). - iOS / macOS — Keychain with
KeychainAccessibility.first_unlock, so background services can open the database after a reboot. - Windows — DPAPI-sealed file (
CryptProtectData) managed byflutter_secure_storage's FFI backend. - Linux — libsecret / Secret Service (GNOME Keyring).
- Android — Jetpack Crypto
- On every open, the key is applied via
PRAGMA keybefore drift issues its first statement (lib/core/database/connection/native.dart). - If the vault is unavailable (headless CI, locked-down hosts), the
CryptoManagerdegrades to an ephemeral in-memory key instead of crashing — the database stays encrypted for the session and re-keys on the next launch. - Web builds fall back to WASM SQLite (SQLCipher cannot run in a browser); treat the web target as a demo surface, not the privacy-grade target.
CI/CD (git.kellsupport.com/mtkell/glp-companion)
Forgejo Actions workflows live in .forgejo/workflows/. The ci.yml
pipeline runs on every push to main and on all pull requests inside the
ghcr.io/cirruslabs/flutter:stable container:
actions/checkout@v4- Mock
.envwith placeholderSUPABASE_URL/SUPABASE_PUBLISHABLE_KEY(the real file is git-ignored; the asset must exist for builds) flutter pub getdart run build_runner build --delete-conflicting-outputs(drift codegen)flutter analyze --fatal-infosflutter test --coverage- Build sanity:
flutter build bundle+flutter build web
Repository: https://git.kellsupport.com/mtkell/glp-companion
Testing strategy
test/features/**— domain unit tests (PK calculator, rotation policy, unit conversions, delta engine), data-layer tests over in-memory drift, and controller tests with fakes.test/features/**/**_screen_test.dart,test/features/nutrition/ nutrition_widget_test.dart— widget tests exercising adaptive layouts and reactive progress meters.test/integration/app_navigation_test.dart— shell-level navigation across all core routes under authenticated, dev-bypass, and unauthenticated conditions, on both rail and bar layouts.
License
Private project — all rights reserved.