A focused tour of how Orbion is wired. Start here before reading code.
Views ──► AI generators ──► Scoring ──► Sync ──► iTunes clients ──► Apple
│ │
└─► Models (SwiftData)
Everything below the Views layer is plain Swift modules. SwiftUI surfaces read from SwiftData and trigger sync; nothing reaches outward from a model.
Orbion/Models/- One file per SwiftData@Model.TrackedApp,TrackedKeyword,KeywordRanking,KeywordPopularity,SerpSnapshot/SerpResult,Competitor+CompetitorKeywordOverlap,AppMetadataSnapshot,Review+ReviewTheme,WeeklyASOReport,AIGenerationLog,UserSettingssingleton,KeywordResearchSnapshot,PendingMetadataChange(PROVE loop), plus theOrbionSchemaregistration.Orbion/iTunes/- Lookup, Search, Reviews-RSS clients. All actor-based, all global (no auth).Orbion/ASC/- Optional App Store Connect API integration.JWTSignermints ES256 tokens from a Keychain-stored.p8;ASCClientis the async actor wrapper with token caching;ASCMetadataFetcherreadsappInfoLocalizations+appStoreVersionLocalizationsand merges them per-locale so the metadata sweep can populate subtitle and the keywords field - fields iTunes Lookup hides.Orbion/Utilities/-Hashing,DateMath,Country(50-market table),Keychain,ArrayChunked,KeywordCSVExport.Orbion/Diff/MetadataDiffer.swift- content-hash + change detection for metadata snapshots.Orbion/Scoring/-CompetitorScorer,OpportunityScorer,KeywordResearchAnalyzer,KeywordGapAnalyzer,DemandIndexScorer,ASOScorer,OutcomeScorer(PROVE loop).
Sync/SyncCoordinator.swift sweeps in order: rank tracking → metadata snapshot → adoption detection → reviews → demand index → outcome scoring.
- Auto-sync on app launch (≥2h cooldown) + after adding a new app.
- Manual
Sync Now(⌘R) and per-keyword "Check Now" available. Sync/CompetitorDiscovery.swiftauto-createsCompetitorrows from top-50 SERP results, recomputes scores after every sweep.Sync/NotificationManager.swiftis a thinUNUserNotificationCenterwrapper. Events: rank crash, competitor change, 1-star reviews, outcome worked, outcome regressed, pending-rewrite nudge.Sync/AdoptionDetector.swiftmatchesPendingMetadataChangerows against new live metadata; auto-detects title/description adoptions; fires a 7-day nudge for subtitle/keywords (manual mark-as-shipped path).
AI/FoundationModelsClient.swiftwraps theFoundationModelsframework behind a single typed funnel:respond<T: Generable>(_:as:). Each public method is a one-liner over that funnel. The whole client is gated behind#if canImport(FoundationModels); the#elsebranch fulfills the same interface and always throws.unavailableso callers don't need to special-case missing-SDK builds.AI/KeywordSuggester.swift- FM-first; falls back to heuristic.AI/ReviewClusterer.swift- FM clustering, keyword-bucket fallback.AI/ReviewReplyDrafter.swift- drafts a single reply per review.AI/MetadataRewriter.swift- generates 3-5 candidate strings for title / subtitle / keywords / description.AI/ReviewKeywordExtractor.swift- pulls user-vocabulary keywords from recent reviews.AI/WeeklyPlanGenerator.swift- heuristic plan as deterministic source of truth, FM enrichment on top.AI/ASORationaler.swift- FM rationale + structured "What to fix next" steps anchored toASOScorerbreakdown.AI/ResearchInsighter.swift- Research view's "What I'd try instead" - positioning angle + long-tail variants.AI/CompetitorIntel.swift- per-competitor FM summary onCompetitorDetailView.AI/ReviewActionPicker.swift- picks one action per review onReviewsTab.
Views/RootView.swift-NavigationSplitView, sidebar with apps, dashboard / research / per-app drill-in.Views/OnboardingView.swift- welcome → country picker → add first app → done.Views/AddAppView.swift- paste URL/ID, country picker, look-up preview, duplicate detection.Views/DashboardView.swift- greeting, stats row, Track record card (PROVE loop), today's movements (deterministic), sync card.Views/ResearchView.swift- pre-launch keyword scouting: verdict, cross-market comparison, FM positioning angle + long-tail variants with one-click Track.Views/SettingsView.swift- three tabs: General, AI, Data.Views/AppDetailView.swift- segmented tab picker for the five tabs.Views/Tabs/OverviewTab.swift- ASO Score circle + structured "What to fix next" steps, stats, quick actions, recent movements.Views/Tabs/KeywordsTab.swift- sortable table with Demand column, 7d delta pill, Add row, gap suggestions chip strip, review-derived suggestions chip strip.Views/KeywordDetailView.swift- Demand sources, SwiftUI Charts trend, top-10 SERP, Check-Now button.Views/Tabs/CompetitorsTab.swift- sortable table, push navigation toCompetitorDetailView.Views/CompetitorDetailView.swift- stats, shared keywords, recent metadata changes, ignore/restore.Views/Tabs/ReviewsTab.swift- theme chips, reviews list with star ratings + country flags + Draft reply +ReviewActionPickerper-review action.Views/Tabs/WeeklyPlanTab.swift- eligibility check, structured "Recommended test" card with Copy proposed text button + markdown rendering.Views/MetadataRewriterSheet.swift- pick field + 1-5 target keywords → 3-5 FM candidates with char counts. Tracking badge on previously-copied candidates: shows adoption status, outcome verdict, best-keyword delta, and a Mark as shipped button for subtitle/keywords.
The wedge that makes Orbion uncategorizable next to ASO trackers:
- User generates candidates in
MetadataRewriterSheetfor a given field (title/subtitle/keywords/description) with 1-5 target keywords. - User clicks Copy on a candidate →
PendingMetadataChangerow inserted with the candidate text, target keywords, and a JSON snapshot of current ranks (the baseline) for those keywords. - On every sync's metadata sweep,
AdoptionDetectorcompares the latestAppMetadataSnapshotagainst unadopted pending rows ≤14 days old. For title and description fields (which iTunes Lookup returns), substring match →adoptedAt = now. For subtitle/keywords (which Apple doesn't expose publicly), the user clicks Mark as shipped in the Rewriter sheet's tracking badge. OutcomeScorerruns after every rank sweep, computes per-target-keyword rank deltas at 7/14/28-day windows post-adoption, picks a verdict (worked / inconclusive / regressed) with a ±3-position threshold, persists asoutcomeJSON.- Dashboard's Track record card rolls all adopted rewrites into a wins ledger.
- Rewriter sheet's tracking badge on each candidate row surfaces prior-rewrite outcomes inline, so users see what worked before generating new candidates.
- iTunes Lookup doesn't return subtitle or keywords field. Without App Store Connect connected, adoption auto-detection only works for title and description; subtitle/keywords rewrites need manual "Mark as shipped" - and a push notification fires once, 7 days after Copy, if the change is still pending. When ASC is connected,
ASCMetadataFetcherpopulates these on the snapshot during the metadata sweep, andAdoptionDetectorauto-detects them like any other field. - Outcome scoring has a 7-day floor post-adoption before any verdict appears. The Rewriter badge surfaces "outcome scoring kicks in at day 7" until then.
- Outcome notifications fire exactly once per change (keyed by change UUID), tracked via
PendingMetadataChange.notifiedVerdictRawandnudgedAt.
Orbion.entitlements- sandbox +network.client+user-selected.read-only.- Synchronized file groups (Xcode 16+) - new Swift files auto-discovered, no
project.pbxprojsurgery needed. - Bundle ID:
jafforge.Orbion(overridable; see README "Local build overrides").
Configs/Local.xcconfig (gitignored) can override DEVELOPMENT_TEAM and PRODUCT_BUNDLE_IDENTIFIER without touching project.pbxproj. See Configs/Local.xcconfig.example.