33 KiB
Architectural Specification: GLP-1 Companion Platform
Document Version: 1.0.0
Target Runtimes: iOS, Android, Web (Wasm/CanvasKit), macOS, Windows
Framework: Flutter 3.x (Dart 3.x)
Security & Compliance Level: HIPAA & GDPR-Ready Tier 1 Health Application
1. System Vision & Domain Boundaries
The GLP-1 Companion is designed to address the clinical and behavioral realities of GLP-1/GIP receptor agonist pharmacotherapy (Semaglutide, Tirzepatide, Liraglutide). The architecture is structured around five core pillars:
- Pharmacokinetics & Dose Logistics: Modeling dosage curves, biological half-lives, titrations, and physical injection site rotation.
- Lean Mass Preservation: Monitoring protein intake targets and hydration volumes to mitigate muscle atrophy and kidney/GI adverse events.
- Recipe Scraping & Pantry Ingestion: De-duplicating grocery overhead via automated microdata extraction and local inventory delta checks.
- Decoupled Biometric Ingestion: Unifying OS-native health ecosystems (HealthKit, Health Connect) with cloud APIs (MyFitnessPal, Withings) across mobile, desktop, and web.
- Zero-Knowledge / High-Privacy Local Persistence: End-to-end encrypted local databases (SQLCipher) paired with passwordless federated authentication.
2. Architectural Paradigm: Feature-First Clean Architecture
The platform rejects monolithic and layered-by-technical-type layouts in favor of Bounded Contexts (Feature-First Clean Architecture). Each business domain operates as an autonomous module.
lib/
├── app/ # Top-level application bootstrap
│ ├── app.dart # Multiplatform root widget
│ ├── observers/ # Routing & state lifecycle observers
│ └── router/ # GoRouter route declarations & redirect guards
├── core/ # Shared foundation & cross-cutting concerns
│ ├── error/ # Failures, exceptions, and error boundary handlers
│ ├── network/ # HTTP/REST clients, retry policies, TLS pinning
│ ├── platform/ # Abstract hardware & OS platform wrappers
│ ├── security/ # SQLCipher key generation, biometric locks, keychain
│ ├── theme/ # Dynamic Material 3 & Apple Cupertino theme engines
│ └── utils/ # DateTime calculations, pharmacokinetics math, formatters
└── features/ # Modular business domains
├── auth/ # Passwordless, OAuth SSO, passkey integration
├── medication/ # Titration tracking, dose logging, injection rotation
├── nutrition/ # Micro/macro logs, protein density, hydration
├── recipes/ # Schema.org JSON-LD web scraper, recipe catalog
├── meal_planner/ # Weekly calendar, portion scaler
├── pantry/ # Food inventory, expiration date manager
└── health_sync/ # HealthKit, Health Connect, MFP abstraction
Clean Architecture Layers per Feature
Within each feature module in lib/features/<feature_name>/, separation of concerns is strictly enforced:
<feature_name>/
├── domain/ # Pure Dart - ZERO external framework imports
│ ├── entities/ # Immutable domain business objects
│ ├── value_objects/ # Validated value types (e.g., DosageMg, ProteinTarget)
│ ├── repositories/ # Abstract repository interfaces
│ └── usecases/ # Isolated business operations (single responsibility)
├── data/ # Infrastructure & data persistence
│ ├── datasources/
│ │ ├── local/ # Drift tables, secure key-value stores
│ │ └── remote/ # Supabase REST, GraphQL, third-party APIs
│ ├── models/ # Data Transfer Objects (DTOs), JSON serialization
│ └── repositories/ # Concrete repository implementations
└── presentation/ # UI & State management
├── controllers/ # Riverpod AsyncNotifiers / StateNotifiers
├── screens/ # Responsive top-level views
└── widgets/ # Reusable components (Adaptive Dialogs, Pickers, Cards)
3. Technology Stack & Multiplatform Dependencies
| Capability | Selection | Rationale |
|---|---|---|
| Core UI Framework | Flutter 3.x (Dart 3.x) | Native AOT compilation on iOS, Android, macOS, and Windows; CanvasKit/Wasm rendering on Web. |
| State Management | Riverpod 2.x (riverpod_generator) |
Compile-time safe dependency injection, automatic provider disposal, testability without BuildContext. |
| Navigation & Deep Linking | go_router 14.x |
Declarative routing, browser URL synchronizer, deep linking for dose alerts and magic links. |
| Encrypted Database | drift + sqlcipher_flutter_libs |
Type-safe reactive SQLite interface with AES-256 transparent database encryption at rest. |
| Secure Vault | flutter_secure_storage |
OS-native credential storage: Apple Keychain, Android EncryptedSharedPreferences, Windows Credential Manager, Linux Secret Service. |
| Backend & Identity | Supabase (PostgreSQL + RLS + GoTrue) | Built-in passwordless authentication, OAuth provider support, Row-Level Security, Edge Functions. |
| Recipe Scraping | Dart html parser + Supabase Edge Node |
Client-side parsing of application/ld+json Schema.org/Recipe with cloud fallback for bot-protected sites. |
| Platform Health Sync | health package (Mobile) + OAuth REST (Web/Desktop) |
Unified interface isolating native HealthKit/Health Connect from cross-platform desktop/web code. |
| Push Notifications | flutter_local_notifications |
Cross-platform offline-scheduled alerts for injections and hydration checks. |
4.7 Phase 3B: Platform Health Integrations & HealthGateway Architecture
4.7.1 System Overview
The HealthGateway subsystem provides an abstracted, platform-agnostic bridge between application domains (Nutrition, Biometrics, Exercise) and external health telemetry ecosystems. It decouples high-level clinical metric analysis from platform-specific APIs (Apple HealthKit, Google Health Connect, Samsung Health) and external enterprise standards (SMART on FHIR), ensuring full cross-platform stability across iOS, Android, macOS, Windows, and Web.
┌──────────────────────────────────┐
│ IHealthGateway (Core) │
└─────────────────┬────────────────┘
│
┌─────────────────────────┬───────────────┴────────────────┬────────────────────────┐
│ (iOS / macOS) │ (Android) │ (Cloud / Enterprise) │ (Windows / Web)
▼ ▼ ▼ ▼
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ Apple HealthKit │ │ Health Connect │ │ SMART on FHIR │ │ Local Fallback │
│ - Weight (lbs) │ │ & Samsung Health│ │ OAuth2 / REST │ │ - Manual inputs │
│ - Active Energy │ │ - Weight (lbs) │ │ - HbA1c panels │ │ - Local SQLite │
│ - Blood Glucose │ │ - Active Burn │ │ - Lipid markers │ │ persistence │
└──────────────────┘ └──────────────────┘ └──────────────────┘ └──────────────────┘
4.7.2 Core Gateway Contracts & Data Payloads
// lib/core/platform/health_gateway.dart
import 'dart:io';
import 'package:flutter/foundation.dart';
enum HealthProviderType {
appleHealth,
healthConnect,
samsungHealth,
enterpriseFhir,
manualFallback,
}
enum HealthConnectionStatus {
notConfigured,
permissionDenied,
connected,
unsupportedPlatform,
}
class HealthDataPayload {
final double? weightLbs;
final double? activeCaloriesToday;
final double? bloodGlucoseMgDl;
final DateTime timestamp;
final HealthProviderType source;
const HealthDataPayload({
this.weightLbs,
this.activeCaloriesToday,
this.bloodGlucoseMgDl,
required this.timestamp,
required this.source,
});
}
abstract class IHealthGateway {
/// Verifies current OS entitlement and read permissions.
Future<HealthConnectionStatus> checkStatus(HealthProviderType provider);
/// Triggers native OS permission request prompt.
Future<bool> requestAuthorization(HealthProviderType provider);
/// Queries biometrics aggregated across the current calendar day.
Future<HealthDataPayload?> fetchLatestBiometrics();
/// Revokes active session bindings or tears down local connections.
Future<void> disconnect(HealthProviderType provider);
}
4.7.3 Platform Support & Fallback Strategy
| Target Platform | Health Provider Bridge | Mechanism / Fallback |
|---|---|---|
| iOS | Apple HealthKit | Native package:health querying HKQuantityTypeIdentifierBodyMass, HKQuantityTypeIdentifierActiveEnergyBurned, HKQuantityTypeIdentifierBloodGlucose. |
| Android | Health Connect | androidx.health.connect.client routing both Google Pixel and Samsung Health sensor feeds via Health Connect runtime. |
| Windows / macOS | Local Mock / Manual DB Driver | Native SDKs bypassed via `kIsWeb |
| Web | Web Storage Fallback | Direct Supabase cloud-synced biometrics and manual modal logging; SDK initialization omitted. |
| Enterprise Cloud | SMART on FHIR | Headless OAuth2 code grant returning standardized HL7 FHIR Observation bundles (HbA1c, metabolic panels, weight). |
4.7.4 Native Entitlements & Manifest Configurations
Android (android/app/src/main/AndroidManifest.xml)
<!-- Declare Health Connect integration and permissions -->
<manifest xmlns:android="[http://schemas.android.com/apk/res/android](http://schemas.android.com/apk/res/android)">
<queries>
<package android:name="com.google.android.apps.healthdata" />
<intent>
<action android:name="androidx.health.ACTION_SHOW_PERMISSIONS_RATIONALE" />
</intent>
</queries>
<uses-permission android:name="android.permission.health.READ_WEIGHT"/>
<uses-permission android:name="android.permission.health.READ_ACTIVE_CALORIES_BURNED"/>
<uses-permission android:name="android.permission.health.READ_BLOOD_GLUCOSE"/>
<application>
<!-- Activity-alias for Health Connect permissions rationale -->
<activity-alias
android:name="ViewPermissionUsageActivity"
android:targetActivity=".MainActivity"
android:exported="true"
android:permission="android.permission.START_VIEW_PERMISSION_USAGE">
<intent-filter>
<action android:name="android.intent.action.VIEW_PERMISSION_USAGE" />
<category android:name="android.intent.category.HEALTH_PERMISSIONS" />
</intent-filter>
</activity-alias>
</application>
</manifest>
iOS (ios/Runner/Info.plist)
<key>NSHealthShareUsageDescription</key>
<string>GLP-1 Companion uses read-only HealthKit data to track lean body mass trends, active caloric expenditure, and glycemic markers alongside your dosing schedule.</string>
<key>NSHealthUpdateUsageDescription</key>
<string>GLP-1 Companion records weight and dose timings into Apple Health to keep clinical history synchronized.</string>
4.7.5 Security, Authorization & Key Management
- Zero Raw Token Exposure: Enterprise SMART on FHIR refresh and access tokens are held exclusively in hardware-backed storage (
flutter_secure_storageutilizing Android Keystore and Apple Keychain). - Read-Only Minimal Principle: Scopes requested from HealthKit and Health Connect are strictly restricted to read permissions for weight, blood glucose, and active burn to maximize user trust and prevent third-party telemetry leakage.
- Local Encryption at Rest: Any synced telemetry data cached locally is stored inside the SQLCipher-encrypted SQLite database (
glp1_encrypted.db), keyed by the hardware-derived master key managed byCryptoManager.
4.8 Phase 3C: Nutritional Search Engine, Barcode Scanning & Dual-Source Food Cache
4.8.1 Subsystem Overview
The Food Ingestion subsystem bridges cloud product databases with encrypted local storage (FoodProductsTable in Drift SQLCipher). To defend against rate-limiting, missing products, and non-profit server downtime (e.g., Open Food Facts 503 limits), the ingestion engine implements a deterministic multi-tier resolution ladder:
- L1 Local Cache: Encrypted SQLite query by exact barcode or tokenized substring.
- L2 Packaged Foods Primary (Open Food Facts): REST API v3 queries identifying branded packaged items with a strict custom
User-Agentand a 4-second timeout. - L3 Whole & Unbranded Foods Fallback (USDA FoodData Central): Automated fallback querying Foundation, SR Legacy, and Survey databases for generic items, unbranded raw foods, or failed L2 lookups.
- L4 Manual Ingestion: Client-side fallback modal allowing user-defined product creation and instant SQLCipher persistence.
┌────────────────────────┐ ┌────────────────────────┐
│ Text Query Search │ │ Mobile Barcode Scan │
│ ("string cheesestick") │ │ (EAN-13, UPC-A) │
└───────────┬────────────┘ └───────────┬────────────┘
│ │
▼ ▼
┌───────────────────────────────────────────────────────────┐
│ IFoodSearchRepository │
└─────────────────────────────┬─────────────────────────────┘
│
┌─────────────────────────┼─────────────────────────┐
▼ (1. Check Cache) ▼ (2. Primary Query) ▼ (3. Auto Fallback)
┌─────────────────────┐ ┌─────────────────────┐ ┌─────────────────────┐
│ Local Drift Cache │ │ Open Food Facts API │ │ USDA FoodData API │
│ (SQLCipher AES-256) │ │ (Branded / Barcode) │ │ (Whole/Raw/Fallback)│
└──────────┬──────────┘ └──────────┬──────────┘ └──────────┬──────────┘
│ (Hit) │ (Success) │ (Success)
│ └────────────┬─────────────┘
│ │ (Write Through Cache)
▼ ▼
┌───────────────────────────────────────────────────────────────────────────┐
│ Normalized FoodProduct │
│ (UPC, Name, Brand, Serving, Calories, Protein, Carbs, Fat, Fiber) │
└─────────────────────────────────────┬─────────────────────────────────────┘
│
┌──────────────────────────┴──────────────────────────┐
▼ ▼
┌──────────────────────────────────────┐ ┌──────────────────────────────────────┐
│ Log to Daily Nutrition │ │ Add to Pantry Inventory │
│ (Inserts into FoodLogsTable) │ │ (Inserts into PantryItemsTable) │
└──────────────────────────────────────┘ └──────────────────────────────────────┘
4.8.2 Domain Model & Contracts
// lib/features/nutrition/domain/entities/food_product.dart
class FoodProduct {
final String id;
final String? barcode;
final String productName;
final String? brand;
final double servingSizeGrams;
final String servingSizeUnit;
final double calories;
final double proteinGrams;
final double carbsGrams;
final double fatGrams;
final double? fiberGrams;
final double? sodiumMg;
final String? imageUrl;
final DateTime lastUpdated;
const FoodProduct({
required this.id,
this.barcode,
required this.productName,
this.brand,
required this.servingSizeGrams,
this.servingSizeUnit = 'g',
required this.calories,
required this.proteinGrams,
required this.carbsGrams,
required this.fatGrams,
this.fiberGrams,
this.sodiumMg,
this.imageUrl,
required this.lastUpdated,
});
}
4.8.3 Persistence Schema (FoodProductsTable)
// lib/core/database/tables/food_products_table.dart
import 'package:drift/drift.dart';
class FoodProductsTable extends Table {
TextColumn get id => text()(); // UPC barcode or prefixed UUID ('usda_12345')
TextColumn get barcode => text().nullable()();
TextColumn get productName => text()();
TextColumn get brand => text().nullable()();
RealColumn get servingSizeGrams => real().withDefault(const Constant(100.0))();
TextColumn get servingSizeUnit => text().withDefault(const Constant('g'))();
RealColumn get calories => real()();
RealColumn get proteinGrams => real()();
RealColumn get carbsGrams => real()();
RealColumn get fatGrams => real()();
RealColumn get fiberGrams => real().nullable()();
RealColumn get sodiumMg => real().nullable()();
TextColumn get imageUrl => text().nullable()();
DateTimeColumn get lastUpdated => dateTime()();
@override
Set<Column> get primaryKey => {id};
}
4.8.4 Data Source Specifications & Nutrient Mapping
| Nutrient Metric | Open Food Facts Nutriments Key | USDA FoodData Central Nutrient ID | Normalization Rule |
|---|---|---|---|
| Energy (Calories) | energy-kcal_serving / energy-kcal_100g |
1008 (Energy) |
Expressed as kcal (double). |
| Protein | proteins_serving / proteins_100g |
1003 (Protein) |
Grams per serving (double). |
| Carbohydrates | carbohydrates_serving / carbohydrates_100g |
1005 (Carbohydrate, by difference) |
Grams per serving (double). |
| Total Fat | fat_serving / fat_100g |
1004 (Total lipid) |
Grams per serving (double). |
| Dietary Fiber | fiber_serving / fiber_100g |
1079 (Fiber, total dietary) |
Grams per serving; nullable. |
| Sodium | sodium_serving / sodium_100g |
1093 (Sodium, Na) |
Converted to mg (OFF returns grams; multiply by 1000). |
4.8.5 Network & Resiliency Rules
- Mandatory User-Agent: All HTTP requests to Open Food Facts must include:
Anonymous or default CLI user-agents will trigger upstream proxy 503 throttling.User-Agent: GLP1Companion-App/1.0 (contact: mtkell@kellengineering.com; platform: Flutter) - Circuit Timeout: Remote calls to Open Food Facts enforce a 4-second timeout. Upon timeout or HTTP 5xx error, the client transparently routes the query to
UsdaFdcClient. - Write-Through Local Caching: Every successfully resolved remote payload from OFF or USDA FDC is written into
FoodProductsTablewithinsertOnConflictUpdate. Subsequent lookups for identical barcodes or query tokens resolve locally with zero network I/O.
4. Multiplatform UX/UI Adaptation Engine
The UI layer implements an Adaptive Design Strategy rather than a lowest common denominator approach:
┌────────────────────────────────────┐
│ Platform Theme Director │
└─────────────────┬──────────────────┘
│
┌───────────────────────┴───────────────────────┐
▼ ▼
[Apple: iOS & macOS] [Material: Android, Web, Win]
┌─────────────────────────────────┐ ┌─────────────────────────────────┐
│ Cupertino Engine │ │ Material 3 Engine │
│ • CupertinoNavigationBar │ │ • NavigationRail / NavigationBar│
│ • CupertinoSliverRefreshControl │ │ • FloatingActionButtons │
│ • SF Pro System Typography │ │ • Roboto / Adaptive Font Scaler │
│ • Haptic Feedback & Swipe-Back │ │ • Dynamic Color / ColorScheme │
└─────────────────────────────────┘ └─────────────────────────────────┘
Breakpoint Matrix
- Compact (< 600dp - Mobile): Bottom
CupertinoTabBarorNavigationBar. Full-screen sheets for dose logging. - Medium (600dp - 1024dp - Foldables / Tablets): Persistent navigation rail, split-view list/detail pane for recipe browsing and pantry audits.
- Expanded (> 1024dp - Desktop / Web): Multi-column dashboard, persistent sidebar navigation rail, keyboard shortcuts (e.g.,
Cmd/Ctrl + Nfor quick food logging), modal popovers rather than full-screen sheets.
5. Security & Passwordless Authentication Architecture
Password-based login is explicitly deprecated to eliminate credential-stuffing vectors and streamline onboarding.
User Device Supabase Auth Gateway OAuth Identity Provider
│ │ │
├─── Selects Provider ───────────┼───────────────────────────────────>│ (Apple / Google / MS)
│ (Apple/Google/Microsoft) │ │
│<── Returns Auth Token/Nonce ───┼────────────────────────────────────┤
│ │ │
├─── Exchange Nonce / Verify ───>│ │
│<── Issue JWT + Refresh Token ──┤ │
│ │ │
│─── Or Request Magic Link ─────>│ │
│ (Email OTP / Deep Link) ├───── Dispatches Secure Link ──────>│ (User Email Box)
│<── Deep Link Activated ────────┤ │
│ │ │
└─── Store Session in Keychain ──┴────────────────────────────────────┘
Zero-Knowledge Local Storage Scheme
Local encryption relies on a three-tier lifecycle:
- On initial app bootstrap, a cryptographic salt and 256-bit AES master key are generated via
core/security/crypto_manager.dart. - The master key is committed directly to the platform hardware vault.
- The SQLCipher SQLite instance initializes using this hardware-backed key.
Platform vault targets:
- iOS / macOS: Apple Keychain Services (
kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly) - Android: Android KeyStore backing
EncryptedSharedPreferences(AES-256 GCM) - Windows: Windows Data Protection API (DPAPI / Credential Manager)
- Web: Ephemeral in-memory key unlocked by biometric WebAuthn / session credentials
SQLCipher database initialization parameters:
PRAGMA key = 'x2C5D8F91B4E6A30C8D7E1F2A5B8C0D3E...';
PRAGMA cipher_page_size = 4096;
PRAGMA kdf_iter = 256000;
PRAGMA cipher_hmac_algorithm = HMAC_SHA512;
6. Pharmacokinetics & GLP-1 Tracking Engine
The medication feature models drug serum concentration to deliver realistic physiological insights rather than simple calendar alarms.
Half-Life Modeling Formulation
Active serum levels are calculated using standard single-compartment open pharmacokinetic decay curves:
C(t) = \sum_{i=1}^{n} D_i \cdot \left(\frac{1}{2}\right)^{\frac{t - t_i}{t_{1/2}}}
Where:
D_i= Dose administered at timestampt_i(mg)t_{1/2}= Elimination half-life of compound:- Semaglutide (Ozempic / Wegovy):
\approx 168\text{ hours (7 days)} - Tirzepatide (Mounjaro / Zepbound):
\approx 120\text{ hours (5 days)} - Liraglutide (Saxenda):
\approx 13\text{ hours}
- Semaglutide (Ozempic / Wegovy):
t= Current evaluation timestamp
Injection Site Rotation Matrix
To prevent lipohypertrophy, injection events map to a 12-quadrant anatomical topological matrix:
- Abdomen: Right Upper, Right Lower, Left Upper, Left Lower
- Thighs: Right Anterior/Lateral, Left Anterior/Lateral
- Upper Arms: Right Tricep/Lateral, Left Tricep/Lateral
Rotation alerts automatically flag and suggest the least recently utilized site.
7. Recipe Parsing & Smart Pantry Delta Engine
┌────────────────────────────────────────────────────────┐
│ Web Scraping Pipeline (Recipes) │
└───────────────────────────┬────────────────────────────┘
│
1. Fetch HTML Document ───────┴───────> 2. Query <script type="application/ld+json">
│
Found Valid Schema.org/Recipe?
├── YES ──> Parse Ingredients & Macros
└── NO ──> Trigger Edge LLM Regex Fallback
│
▼
[Parsed Recipe Entity]
- Title & Yield
- Ingredient List & Units
- Protein / Carb / Fat Counts
│
▼
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Smart Pantry Delta Generator │
│ │
│ [Weekly Meal Plan] [Active Pantry Inventory] [Shopping List Engine] │
│ • 800g Chicken Breast ─── Compare ──> • 300g Chicken Breast ──────> • Add 500g Chicken │
│ • 4 Avocados ─── Compare ──> • 4 Avocados ──────> • Item Satisfied (0) │
│ • 100g Greek Yogurt ─── Compare ──> • Empty ──────> • Add 100g Greek Yog. │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
8. Cross-Platform Health Data Integration (HealthGateway)
Cross-platform parity requires an abstract interface with decoupled implementations:
// lib/core/platform/health_gateway.dart
abstract class IHealthGateway {
Future<bool> requestPermissions();
Future<List<BiometricEntry>> fetchWeightHistory({required DateTime start, required DateTime end});
Future<List<ExerciseEntry>> fetchWorkouts({required DateTime start, required DateTime end});
Future<void> pushNutritionalIntake({required NutritionalRecord record});
Stream<BiometricEntry> get realTimeWeightStream;
}
Implementation Matrix
- iOS (
health_gateway_apple.dart): WrapsHealthKit(HKQuantityTypeIdentifierBodyMass,HKQuantityTypeIdentifierActiveEnergyBurned). - Android (
health_gateway_android.dart): WrapsHealth Connect API(WeightRecord,ExerciseSessionRecord). - Desktop / Web (
health_gateway_cloud.dart): Communicates with cloud partner REST endpoints (MyFitnessPal API, Withings API, Google Fit REST) via OAuth token grants, falling back to manual entry dialogs.
9. Error Handling, Telemetry & Extensibility Architecture
Functional Error Handling
The codebase bans throwing unhandled raw exceptions across layer boundaries. All use-cases return a functional Result<T, Failure> union pattern (fpdart / freezed):
typedef AsyncResult<T> = Future<Either<Failure, T>>;
Modular Plugin Registration (Open-Closed Principle)
To support post-launch capabilities (e.g., continuous glucose monitor [CGM] Bluetooth LE integration, Telehealth lab panels), the app exposes a modular plugin registry:
abstract class ICompanionPlugin {
String get pluginId;
String get displayName;
Future<void> initialize(ProviderContainer container);
List<RouteBase> get pluginRoutes;
Widget buildDashboardWidget(BuildContext context);
}
Plugins declare isolated database tables, register their own routes into GoRouter, and inject dashboard summary tiles without altering the core application codebase.