Youll
Architecture

Architecture Overview

How the Youll platform is structured across three layers, from the shared Bricks core to customer-specific configuration.

Why This Architecture

Youll exists to solve a specific problem: shipping multiple branded mobile apps from a single shared codebase. Each customer app needs to look and feel unique, with its own colors, typography, content, and optional features, but the underlying screens, navigation, business logic, and networking are the same across all of them.

The architecture uses a git submodule approach rather than a published package registry. This is intentional. Bricks evolves alongside customer apps, and submodules give each app a pinned version of the shared code that can be updated on its own schedule. There is no version resolution to worry about, no registry to maintain, and no risk of a published update breaking a customer app in production.

The core principle is simple: customer apps contain zero business logic. Every screen, view model, coordinator, API call, and data model lives in Bricks. The customer app is purely a configuration layer that provides API keys, design tokens, SDK wrappers, and module selection.

Product Modules vs Technical Modules

Youll has two levels of modularity that are important to understand:

Product modules are the features a customer chooses to activate. From a sales and product perspective, Home, Explore, Journeys, Library, Events, Activity, Meals, Search, Timer, Biometrics, Community, and Tuya are all separate modules. A customer decides: "We want Home, Explore, and Journeys, but not Events or Meals."

Technical modules are the 13 Swift packages under Bricks/modules/. These are how the code is organized for compilation and dependency management.

The two don't map one-to-one. Most product modules (Home, Explore, Search, Library, Journeys, Events, Activity, Meals, Quiz, Favorites, Playlists) are implemented inside the Core and Client technical modules rather than in their own Swift packages. This is because these features share a significant amount of UI infrastructure, theming, and data services. Only features that are truly independent and optional at a technical level (Timer, Biometrics, Community, Tuya) get their own Swift packages.

How Product Modules Are Activated

Product module activation happens at two levels:

1. Tab selection. The customer app implements GenericConfigFactory.makeTabItems() to declare which tabs appear in the app. This is the primary mechanism for choosing which product modules are visible:

// A content-focused app
func makeTabItems() -> [TabItem] {
    [.home, .explore, .library, .journeys, .profile]
}

// A hardware + wellness app (using Showcase v2)
func makeTabItems() -> [TabItem] {
    [.showcase, .biometrics, .profile]
}

// An app using the newest home screen (Slices v3 + REST)
func makeTabItems() -> [TabItem] {
    [.slices, .explore, .library, .profile]
}

Available tab types include: .home (Home v1), .showcase (Home v2), .slices (Home v3), .explore, .library, .journeys, .events, .activity, .meals, .biometrics, .community, .profile, and more.

2. Feature flags. Firebase Remote Config provides granular control over feature behavior within tabs. The Flag enum in Core defines toggles like journeysEnabled, eventsEnabled, mealsEnabled, libraryEnabled, exploreEnabled, quizEnabled, isLightVersion, and others. These allow features to be turned on or off without a code change.

3. Settings. The customer app sets persistent configuration in AppDelegate via the Settings actor. For example, Settings.isSearchEnabled = false disables the search feature entirely.

When the documentation refers to "modules," it means product modules unless specifically stated as "technical modules" or "Swift packages." This matches how the Youll team and customers talk about the platform.

The Three Layers

Layer 1: Bricks (Shared Core)

Bricks lives as a git submodule at Bricks/ inside every customer app. It contains 13 independent Swift packages under Bricks/modules/, each with its own Package.swift. There is no single root Package.swift that ties them all together. Instead, modules declare local path dependencies on each other.

The Bricks/ directory also contains:

  • Library/: vendored binary .xcframework files (SDWebImage, SnapKit, Lottie, AppsFlyerLib, BrazeKit, BrazeUI, Pulse, and others). Modules reference these via relative paths in their Package.swift files rather than fetching them from a remote registry.
  • Resources/CoreLocalizable.xcstrings: shared localization strings used across modules.

The 13 technical modules fall into three categories:

Infrastructure (everything depends on these):

ModuleRole
YoullNetworkGraphQL (Apollo) + REST dual networking layer. All API calls go through here.
CoreFoundation layer and the home of most product module screens. Contains the coordinator protocol, module interface protocols, settings, feature flags, UI infrastructure, theming, and all screens for Home, Explore, Search, Library, Journeys, Events, Activity, Meals, Quiz, Favorites, and Playlists.
ClientOrchestration layer containing the Config singleton, AppCoordinator, MainCoordinator, all tab coordinators (HomeCoordinator, ExploreCoordinator, LibraryCoordinator, JourneysCoordinator, etc.), screen config factory protocols, analytics, and push notifications.

Feature modules (separate Swift packages, activated per customer):

ModuleRole
PlayerAudio/video playback, mini player, workout player, map routes, Now Playing integration.
ProfileUser profile, settings, subscriptions and SuperPaywall, reminders, badges, Apple Health UI, sharing.
OnboardingAuth flows (email, Apple Sign-In, Google Sign-In, anonymous), onboarding screens, email verification.
TimerMeditation/session timer with bell intervals, background sounds, and IoT device integration.
BiometricsHealthKit integration for HRV, heart rate, sleep, steps, calories, and workout data.
CommunitySocial features: channels, posts, replies, reactions, and admin moderation.
TuyaIoT device integration: 5 device types, BLE/WiFi pairing, scheduling, and maintenance.
CoreWidgetiOS home screen widgets for content recommendations and streak tracking.

Standalone (no Bricks dependencies):

ModuleRole
TimerSharedShared types between the Timer module and the widget extension. Contains ActivityKit live activity models.
UITestsShared UI test infrastructure with reusable test helpers for all app sections.

Core is ~672 source files because it contains the screen implementations (view controllers, view models, views) for all the core product modules: Home, Explore, Search, Library, Journeys, Events, Activity, Meals, Quiz, Favorites, and Playlists. Client provides the coordinators that wire these screens together. This is why there is no separate "Content" or "Explore" Swift package.

Layer 2: Customer App (Customization Layer)

The customer app is the host Xcode project that wraps Bricks. It has four responsibilities:

1. Configuration injection. The AppDelegate calls a series of setup functions to inject API keys, environment URLs, screen configurations, UI theming, and analytics into Bricks:

Client.setup(with: Keys)
Core.setup(with: Keys)
Client.setupScreenConfigFactory(with: ScreenConfigFactory())
Client.setupUIConfigurator(with: UIConfigurator.shared)
Client.AnalyticsManager.setup(with: AppAnalytics.shared)
Client.Config.setup(with: AppEnvironment.current)

2. UI customization via design tokens. The customer app bundles JSON files exported from Figma (tokens.json, colors.json, sizes.json, textStyles.json) and parses them at runtime through ConfigParser and TypographyParser. These parsed values feed into screen config factories that control the appearance of every screen in the app.

3. SDK wrappers. When a Bricks module needs to interact with a third-party SDK, the customer app provides the concrete implementation. For example, the Tuya module defines a TuyaControllerProtocol in Core, and the customer app provides a TuyaController class that wraps the actual Tuya IoT SDK. This keeps third-party SDK dependencies out of Bricks.

4. Protocol implementations. The customer app implements several protocols defined in Bricks:

ProtocolDefined InPurpose
ScreenConfigFactoryTypeClientUmbrella protocol with ~20 sub-factory protocols for per-screen visual configuration
UIConfigCoreButton styling, navigation bar styling, tab bar styling, gradient generation
AnalyticsManagerTypeClientAnalytics event forwarding to Firebase, AppsFlyer, Facebook, Braze
TuyaControllerProtocolCoreIoT device management wrapping the Tuya SDK
CoreKeysType / ClientKeysTypeCoreAPI keys, app store IDs, legal URLs, deeplink prefixes

Layer 3: Module Injection at Runtime

The four feature modules that can be activated per customer (Timer, Tuya, Biometrics, Community) use a protocol-based injection pattern. Core defines the interface protocols so that Client's AppCoordinator can reference these modules without compile-time dependencies on their packages.

The four module interface protocols:

public protocol TimerModuleInterface: AnyObject {
    var isOnScreen: Bool { get }
    func showTimerSettingsController(from: UINavigationController, tuyaController: TuyaControllerProtocol?)
    func showTimerViewController(from: UINavigationController, tuyaController: TuyaControllerProtocol?)
}

public protocol TuyaModuleInterface {
    func getTuyaController() -> TuyaControllerProtocol?
    func createTuyaController(for device: TuyaDevice, ...) -> AppearanceCoordinatedViewController?
    func createTuyaPairController(onClose: (() -> Void)?) -> UIViewController?
}

public protocol BiometricsModuleInterface: AnyObject {
    func createCoordinator(delegate: BiometricsModuleInterfaceDelegate?) -> Coordinator
}

public protocol CommunityModuleInterface: AnyObject {
    func createCoordinator(delegate: CommunityModuleInterfaceDelegate?) -> Coordinator
}

In SceneDelegate, the customer app creates concrete instances of the modules it needs and passes them to AppCoordinator:

coordinator = AppCoordinator(
    window: sceneWindow,
    config: config,
    showSplashVideo: false,
    splashScreen: SplashViewController(),
    timerModuleInterface: TimerModule(
        screenConfigFactory: TimerConfigFactory(),
        analytics: analyticsManager,
        requiredNetworkCheckPublisher: config.requiredNetworkCheckPublisher,
        contentService: contentService,
        userService: config.userService),
    tuyaModuleInterface: TuyaModule(screenConfigFactory: TuyaConfigFactory()),
    biometricsModuleInterface: BiometricsModule(
        screenConfigFactory: BiometricsConfigFactory(),
        uiConfigurator: uiConfigurator,
        userService: config.userService,
        appleHealthDelegate: appleHealthManager)
)
coordinator.start()

All four module parameters are optional. A meditation app that doesn't use IoT devices or health tracking simply omits those parameters. Bricks handles the absence gracefully, hiding related UI elements when a module is not injected.

Module Dependency Graph

The modules form a clear dependency hierarchy. YoullNetwork sits at the bottom with no Bricks dependencies. Core builds on YoullNetwork and defines the interfaces everything else uses. Client sits at the top, orchestrating all the pieces.

YoullNetwork (no Bricks deps)


   Core (depends on YoullNetwork)

    ├──▸ Biometrics (Core)
    ├──▸ Community (Core, YoullNetwork)
    ├──▸ CoreWidget (Core, YoullNetwork)
    ├──▸ Player (Core, YoullNetwork)
    ├──▸ Profile (Core, YoullNetwork)
    ├──▸ Onboarding (Core, YoullNetwork)
    ├──▸ Timer (Core, TimerShared)
    ├──▸ Tuya (Core, YoullNetwork)
    └──▸ Client (Core, Onboarding, Player, Profile)

TimerShared (standalone, no deps)
UITests (standalone, no deps)

Key observations:

  • Client depends on three feature modules directly: Onboarding, Player, and Profile. These are always active in every customer app, so a compile-time dependency is acceptable.
  • Timer, Tuya, Biometrics, and Community are optional. They are never imported by Client. Instead, Client references their interface protocols (defined in Core) and receives concrete instances at runtime.
  • TimerShared exists separately from Timer so that the widget extension can import it without pulling in Timer's full dependency tree.

For the full visual dependency map with external dependencies (Firebase, Apollo, Tuya SDK), see the Dependency Graph page.

How Customer Apps Consume Bricks

Setting up a new customer app involves four steps:

1. Add Bricks as a git submodule:

git submodule add <bricks-repo-url> Bricks

2. Reference Bricks Swift packages in your Xcode project. Add each module your app needs as a local Swift package dependency pointing to its directory under Bricks/modules/.

3. Configure Bricks in AppDelegate. The setup calls must happen in a specific order because later calls depend on earlier ones. See the Bootstrap Sequence page for the full initialization flow.

4. Instantiate and inject modules in SceneDelegate. Create the modules your app needs and pass them to AppCoordinator. Call coordinator.start() to launch the app.

What Lives Where

When adding new functionality, use this guide to determine where the code belongs:

Code TypeWhere It LivesWhy
Screens, view controllers, view modelsBricks modulesShared across all customer apps
Navigation and coordinatorsClient (tab coordinators) or feature modulesCoordinators manage screen flow
API calls and data modelsYoullNetworkSingle networking layer for all modules
Module interface protocolsCoreAllows optional modules without compile-time deps
Base coordinator protocol, settings, feature flagsCoreFoundation used by everything
API keys, environment URLsCustomer app (Keys struct)Unique per customer
Design tokens (colors, typography, sizes)Customer app (JSON files + parsers)Unique per customer's brand
Screen config factoriesCustomer appPer-screen visual customization
Third-party SDK wrappersCustomer appKeeps SDK dependencies out of Bricks
UI theming (UIConfigurator)Customer appImplements the UIConfig protocol from Core

The guiding principle: if two customer apps would share the same code, it belongs in Bricks. If each customer would have a different implementation, it belongs in the customer app.

For Agents

When onboarding to a Youll codebase, follow this reading order:

  1. Start with Core's module interfaces at Bricks/modules/Core/Core/App/Module Interface/. These four protocol files tell you which modules are optional and what capabilities they expose.
  2. Read Client's AppCoordinator at Bricks/modules/Client/Client/Coordinators/AppCoordinator.swift. This shows how the app bootstraps and how modules are wired together.
  3. Read Client's MainCoordinator at Bricks/modules/Client/Client/Coordinators/Main/MainCoordinator.swift. This shows the tab bar structure and how child coordinators are managed.
  4. Check the customer app's AppDelegate and SceneDelegate to understand which modules are active and how configuration is injected.
  5. For any specific screen, look in the relevant Bricks module first (the screen's view controller and view model), then check the customer app's screen config factory to understand visual customization.

When modifying a screen or feature, always check:

  • Does the change belong in Bricks (shared) or the customer app (specific)?
  • If adding a new optional module, define the interface protocol in Core and the implementation in a new module package.
  • If adding a new screen, add a corresponding screen config factory protocol in Client's FactoryTypes.swift.

What's Next

On this page