# Kell Creations — Cross-Platform Shell Composition Strategy ## Overview This document describes the shared composition pattern that all Kell Creations app targets (`kell_web`, `kell_mobile`, future targets) must follow. The pattern lives in the `core` package and provides a consistent way to: 1. Read runtime configuration from `--dart-define` values. 2. Construct environment-appropriate services (fake vs. real backends). 3. Expose services and configuration to the widget tree. ## Architecture ``` ┌──────────────────────────────────────────────────────────────┐ │ core (shared package) │ │ │ │ KcAppConfig / KcAppEnvironment │ │ └─ Runtime config from --dart-define │ │ │ │ KcAppServices (abstract) │ │ └─ Base class for app service containers │ │ │ │ KcServiceFactory │ │ └─ Generic factory: createFake() / createWordPress(cfg) │ │ │ │ KcBootstrap │ │ └─ Shared bootstrap: env switch + WP credential fallback │ │ │ │ KcAppScope │ │ └─ InheritedWidget exposing T + KcAppConfig to tree │ └──────────────────────────────────────────────────────────────┘ ▲ ▲ │ │ ┌────────┴───────────┐ ┌────────────┴──────────────┐ │ kell_web (app) │ │ kell_mobile (app) │ │ │ │ │ │ AppServices │ │ MobileAppServices │ │ extends │ │ extends │ │ KcAppServices │ │ KcAppServices │ │ │ │ │ │ AppScope │ │ (uses KcAppScope │ │ extends │ │ ) │ │ KcAppScope │ │ │ │ │ │ │ │ │ │ │ │ Bootstrap │ │ (uses KcBootstrap.run │ │ delegates to │ │ with own factory) │ │ KcBootstrap.run │ │ │ └─────────────────────┘ └────────────────────────────┘ ``` ## Why abstract services? Concrete service containers hold references to feature-package repository types (e.g., `InventoryRepository`, `ProductPublishingRepository`). Moving those references into `core` would create **circular dependencies**: ``` core → feature_wordpress → core ← NOT ALLOWED ``` By keeping `KcAppServices` abstract in `core`, the package owns the composition **contract** without knowing concrete types. Each app provides its own concrete subclass. ## Contract for new app targets When creating a new app target (e.g., `kell_mobile`), follow this pattern: ### 1. Create a concrete services class ```dart // apps/kell_mobile/lib/composition/mobile_app_services.dart import 'package:core/core.dart'; import 'package:feature_inventory/feature_inventory.dart'; import 'package:feature_wordpress/feature_wordpress.dart'; // ... other feature imports class MobileAppServices extends KcAppServices { final InventoryRepository inventoryRepository; final ProductPublishingRepository productPublishingRepository; // ... other repositories as needed const MobileAppServices({ required this.inventoryRepository, required this.productPublishingRepository, }); factory MobileAppServices.fake() { return MobileAppServices( inventoryRepository: FakeInventoryRepository(), productPublishingRepository: FakeProductPublishingRepository(), ); } factory MobileAppServices.wordpress({ required String siteUrl, required String consumerKey, required String consumerSecret, }) { final apiClient = WooCommerceApiClient( siteUrl: siteUrl, consumerKey: consumerKey, consumerSecret: consumerSecret, ); return MobileAppServices( inventoryRepository: FakeInventoryRepository(), productPublishingRepository: WordPressProductPublishingRepository(apiClient: apiClient), ); } static KcServiceFactory get serviceFactory { return KcServiceFactory( createFake: () => MobileAppServices.fake(), createWordPress: (config) => MobileAppServices.wordpress( siteUrl: config.wcSiteUrl, consumerKey: config.wcConsumerKey, consumerSecret: config.wcConsumerSecret, ), ); } } ``` ### 2. Wire up main.dart ```dart // apps/kell_mobile/lib/main.dart import 'package:core/core.dart'; import 'package:flutter/material.dart'; import 'composition/mobile_app_services.dart'; void main() { final config = KcAppConfig.fromEnvironment(); final (:services, config: effectiveConfig) = KcBootstrap.run( config, MobileAppServices.serviceFactory, ); runApp( KcAppScope( services: services, config: effectiveConfig, child: const KellMobileApp(), ), ); } ``` ### 3. Access services in widgets ```dart // Anywhere in the widget tree: final services = KcAppScope.of(context); final config = KcAppScope.configOf(context); ``` ## Backward compatibility (kell_web) The `kell_web` app retains its original class names (`AppConfig`, `AppScope`, `Bootstrap`, `AppServices`) as thin wrappers/typedefs around the shared `core` types. Existing code continues to work without modification: | Original (kell_web) | Shared (core) | Relationship | | ------------------- | ------------------ | --------------- | | `AppConfig` | `KcAppConfig` | typedef | | `AppEnvironment` | `KcAppEnvironment` | typedef | | `AppServices` | `KcAppServices` | extends | | `AppScope` | `KcAppScope` | extends (typed) | | `Bootstrap` | `KcBootstrap` | delegates | New code in `kell_web` may use either the original names or the shared `Kc`-prefixed names. The shared names are preferred for clarity and consistency with `kell_mobile`. ## Runtime configuration All app targets use the same `--dart-define` keys: | Key | Description | Default | | ----------------------- | ------------------------------------ | ------- | | `KC_ENV` | `fake` or `wordpress` | `fake` | | `KC_WC_SITE_URL` | WordPress site URL | (empty) | | `KC_WC_CONSUMER_KEY` | WooCommerce REST API consumer key | (empty) | | `KC_WC_CONSUMER_SECRET` | WooCommerce REST API consumer secret | (empty) | When `KC_ENV=wordpress` but credentials are missing, `KcBootstrap` automatically falls back to fake mode with a debug-mode warning. ## What remains app-specific The following concerns are **not** shared and remain in each app target: - **App shell / navigation** — Web uses `NavigationRail`, mobile will use `BottomNavigationBar` or similar. - **Routing** — Route definitions and page builders are platform-specific. - **Platform-specific presentation** — Layout, responsive breakpoints, etc. - **Cross-feature navigation handoffs** — Wired in the app's routing layer. The `design_system` package provides shared visual components (widgets, theme, typography, breakpoints) that both web and mobile can use for consistent styling.