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

Info.plist
Localizable.xcstrings
SoarStarterSwift.entitlements
SoarStarterSwiftApp.swift
AppEnvironment.swift
AppDelegate.swift
RootView.swift
AuthGate.swift
UITestingReset.swift
AppConfig.xcconfig
Secrets.xcconfig
SoarStarterSwiftTests
SoarStarterSwiftUITests

App/ — composition root

Where the app is assembled. Nothing here is a feature; it's the wiring that makes features run.

FileResponsibility
SoarStarterSwiftApp.swift@main entry. Builds one AppEnvironment and injects it into RootView.
AppEnvironment.swiftHand-rolled DI container — constructs every controller and repository in its init.
AppDelegate.swift@UIApplicationDelegateAdaptor for APNs registration and push-tap forwarding.
RootView.swiftComposes the gate stack (ErrorBoundary → Biometric → Update → Onboarding → Consent → Auth) and mounts the toast overlay.
AuthGate.swiftGuest-first entry: the main shell is always available; protected surfaces raise the login flow as a cover.
UITestingReset.swiftWipes 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.
  • AuthSessionController (maps authStateChanges to @Observable).
  • Biometric / Update / Consent — three of the launch gates.
  • PurchasesPurchasesController + PremiumGate (RevenueCat).
  • Locale / ThemeLocaleController and ThemeController plus their enums and, for Theme, the Color+Tokens / Typography design 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.

FolderContents
LocalAppPreferences — an @Observable UserDefaults wrapper.
ModelsCodable/Sendable structs: Todo, Profile, Subscription, AppNotification.
NetworkNetworkObserver (NWPathMonitor@Observable isOnline).
RemoteSupabaseClientProvider, PushTokenRegistrar, PushTestSender, and the per-model remote data sources.
RepositoriesAuthRepository, 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, plus Social/ (Apple + Google) and pure-Swift Validation/.
  • 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 live ComponentGalleryView (English-only demo catalog).
  • Guide — bundled developer guides (some gated behind PremiumGate).
  • Showcase — the removable demo catalog. Deleting it is a documented one-way cut.

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) and functions/ (revenuecat-webhook, send-test-push, delete-account). See Supabase Setup.
  • AppConfig.xcconfig (tracked, safe placeholders) #include?s Secrets.xcconfig (git-ignored). See Configuration.
  • SoarStarterSwiftTests/ (Swift Testing) and SoarStarterSwiftUITests/ (XCUITest smoke). Both use PBXFileSystemSynchronizedRootGroup, so dropping a *.swift file into the folder adds it to the target automatically.

Where a new feature goes

Adding "Projects" as an example:

ModelData/Models/Project.swift (Codable, Sendable).
Remote + repositoryData/Remote/SupabaseProjectsRemoteDataSource.swift behind a ProjectsRepositoryProtocol in Data/Repositories/.
Register the repository in App/AppEnvironment.swift.
View model + viewsFeatures/Projects/ (@MainActor @Observable, UiState<[Project]>).
Route → add a case to the relevant enum in Navigation/MainRoute.swift and a navigationDestination in the shell.

On this page