The composition root. Wires every other module into a single running app and owns the top-level navigation, profile selection, and provider registry.
AppState— the@Observable @MainActoraggregate root. Holds theProfilesModel, the active[ResolvedAccount], theProviderRegistry, Trakt service, Top Shelf publisher, and the active settings models for the current profile. Orchestrates:- profile switching (
switchProfile,saveProfile,removeProfile,rebuildSettingsModels), - Plex Home-user activation (
pendingPlexPINRequest,submitPlexPIN,cancelPlexPIN— the PIN is never stored), - per-profile Trakt namespace swap (
updateTraktForActiveProfile).
- profile switching (
RootView+MainTabView— the root SwiftUI hierarchy (Auth → ProfileSelection → MainTab). Lives here because it must depend on every feature module.ProfileSelectionView— hostsFeatureProfiles.ProfilePickerViewand the create/edit sheet for both the launch picker and Settings.SystemProfileBridge— narrow seam onto the tvOSTVUserManagerAPI (shouldStorePreferencesForCurrentUser) for theuser-managemententitlement.AddAccountView— first-run "add another server" flow that routes intoFeatureDiscoveryandFeatureAuth.LibraryDiscoveryModel— per-account "what libraries does this server expose?" coordination.MediaItemActionCoordinator— the cross-feature action bus (play, resume, mark watched, open detail) so any view can request an action without knowing the playback / detail routing.PlaybackEngineComposition— the only module that importsEnginePlozzigen(and any future engine packages). Builds theEngineFactoryclosure injected intoFeaturePlayback, keeping the heavy on-device decode binaries out of every other module's dependency graph.AppInfo— version / build / display-name helpers read fromInfo.plist.
- Composition root, not a feature. This is the only module
allowed to import all the others. Feature modules don't import
AppShell. - Tokens stay in their stores.
AppStatemay read a session, but it never persists tokens itself — that'sFeatureAuth's job. - Provider-agnostic above the registry.
AppStateresolves providers viaProviderRegistry.provider(for:); nothing here switches onProviderKindexcept for explicitly provider-specific UX (e.g. Plex Home-user PIN). - Profile-namespacing on switch. Every per-user model must be
rebuilt on profile change (
rebuildSettingsModels) so settings, Trakt, and watched-state stay isolated.
AppState.swift— the orchestration entry point.RootView.swift+MainTabView.swift— the root navigation.PlaybackEngineComposition.swift— how the Plozzigen (AetherEngine) engine is injected without leaking past this module.