Project Structure
How the App, Core, Data, Features, and Navigation layers fit together.
The template is organized by role, not by feature: cross-cutting services
live in Core/, data access in Data/, and each screen family in Features/.
Knowing which layer a file belongs to is most of the battle when you add your
own feature. This page walks the tree and says what goes where.
The tree
App/ — composition root
Where the app is assembled. Nothing here is a feature; it's the wiring that makes features run.
| File | Responsibility |
|---|---|
SoarStarterSwiftApp.swift | @main entry. Builds one AppEnvironment and injects it into RootView. |
AppEnvironment.swift | Hand-rolled DI container — constructs every controller and repository in its init. |
AppDelegate.swift | @UIApplicationDelegateAdaptor for APNs registration and push-tap forwarding. |
RootView.swift | Composes the gate stack (ErrorBoundary → Biometric → Update → Onboarding → Consent → Auth) and mounts the toast overlay. |
AuthGate.swift | Guest-first entry: the main shell is always available; protected surfaces raise the login flow as a cover. |
UITestingReset.swift | Wipes the Keychain when launched with -UITestingResetState so UI tests start clean. |
AppEnvironment is the map of what the app can do — reading its init lists
every controller and repository in one place. Start there when you're lost.
Core/ — cross-cutting services
One subfolder per service. Each owns a controller (usually @MainActor @Observable) plus any views tied to that service. These are app-wide concerns
that features consume, not screens themselves.
Analytics/Sentry— observability.AnalyticsController(PostHog, consent-gated),SentryReporter+ErrorBoundary.Auth—SessionController(mapsauthStateChangesto@Observable).Biometric/Update/Consent— three of the launch gates.Purchases—PurchasesController+PremiumGate(RevenueCat).Locale/Theme—LocaleControllerandThemeControllerplus their enums and, for Theme, theColor+Tokens/Typographydesign tokens.UI— the reusable component library (AppButton,AppTextField,UiState, …). See UI Components.Toast/Haptics/Rating/DeepLinks/Notifications/Result— the remaining shared utilities.
Data/ — the data layer
The repository stack, split so tests can inject fakes at two seams. See Data Layer for the full pattern.
| Folder | Contents |
|---|---|
Local | AppPreferences — an @Observable UserDefaults wrapper. |
Models | Codable/Sendable structs: Todo, Profile, Subscription, AppNotification. |
Network | NetworkObserver (NWPathMonitor → @Observable isOnline). |
Remote | SupabaseClientProvider, PushTokenRegistrar, PushTestSender, and the per-model remote data sources. |
Repositories | AuthRepository, TodosRepository, ProfileRepository, UploadsRepository, SubscriptionRepository. |
Features/ — one folder per screen family
Each folder is a self-contained screen family: its views plus (where there's
logic) a @MainActor @Observable view model. Most are Example surfaces
built to be replaced.
Main— the shell:MainShellView(4 tabs + side drawer) + its view model.Auth— login / sign-up / forgot flows, plusSocial/(Apple + Google) and pure-SwiftValidation/.Home/Todos/Upload/Profile/Refer— the replace-me example features. Todos is the reference CRUD screen.Paywall/ManageSubscription/RedeemCode— the monetization surfaces.Onboarding/Legal/Permissions/Notifications— gate and service screens.Component— the liveComponentGalleryView(English-only demo catalog).Guide— bundled developer guides (some gated behindPremiumGate).Showcase— the removable demo catalog. Deleting it is a documented one-way cut.
Navigation/
MainRoute.swift holds the per-tab Hashable route enums (HomeRoute,
ComponentRoute, MeRoute, …) plus the MainTab enum. MainShellView feeds
each tab's route into its own NavigationStack(path:).
The template's CLAUDE.md lists Navigation/AppRoute.swift and
AuthRoute.swift — those files no longer exist. Auth routes are now a private
enum inside App/AuthGate.swift, and gate composition replaced the top-level
route. Code wins over the doc.
Backend & config (outside the app target)
supabase/—migrations/(billing_entitlements.sql) andfunctions/(revenuecat-webhook,send-test-push,delete-account). See Supabase Setup.AppConfig.xcconfig(tracked, safe placeholders)#include?sSecrets.xcconfig(git-ignored). See Configuration.SoarStarterSwiftTests/(Swift Testing) andSoarStarterSwiftUITests/(XCUITest smoke). Both usePBXFileSystemSynchronizedRootGroup, so dropping a*.swiftfile into the folder adds it to the target automatically.
Where a new feature goes
Adding "Projects" as an example:
Data/Models/Project.swift (Codable, Sendable).Data/Remote/SupabaseProjectsRemoteDataSource.swift behind a ProjectsRepositoryProtocol in Data/Repositories/.App/AppEnvironment.swift.Features/Projects/ (@MainActor @Observable, UiState<[Project]>).Navigation/MainRoute.swift and a navigationDestination in the shell.