Files
Reflect/hardening-report.md
treyt d41ba29939 fix: issue #145 - Onboarding subtext
Automated fix by Tony CI v3.
Refs #145

Co-Authored-By: Claude <noreply@anthropic.com>
2026-02-26 20:33:47 -06:00

124 KiB
Raw Permalink Blame History

Hardening Audit Report

Audit Sources

  • 11 mapper agents (100% file coverage)
  • 18 specialized domain auditors (parallel)
  • 1 cross-cutting deep audit (parallel)
  • Total source files: 163

CRITICAL — Will crash or lose data

BUG — Incorrect behavior

SILENT FAILURE — Error swallowed or ignored

RACE CONDITION — Concurrency issue

LOGIC ERROR — Code doesn't match intent

PERFORMANCE — Unnecessary cost

ACCESSIBILITY — Usability barrier

SECURITY — Vulnerability or exposure

MODERNIZATION — Legacy pattern to update

DEAD CODE / UNREACHABLE

FRAGILE — Works now but will break easily


Summary


SOURCE: Memory Auditor (22 findings)

Shared/Services/ImageCache.swift:24 | CRITICAL | NotificationCenter observer token leaked — never stored or removed

  • What: NotificationCenter.default.addObserver(forName:object:queue:using:) is called in init(). The returned opaque observer token is discarded entirely with no variable. The block-based API requires the token to be stored and passed to removeObserver(_:) in deinit.
  • Impact: Each ImageCache initialization registers an observer that is never deregistered. In extension contexts where ImageCache might be initialized more than once, each initialization accumulates another dangling registration that fires clearCache() on every memory warning.

Shared/Persisence/DataController.swift:51-53 | CRITICAL | editedDataClosure array grows forever — no removal mechanism

  • What: addNewDataListener(closure:) appends closures to editedDataClosure with no paired remove function. Every call site permanently stores its closure inside the singleton DataController.shared. If those closures capture self (the view model), the view model is kept alive by the singleton indefinitely.
  • Impact: View models and any objects they capture are never deallocated. Each navigation to a screen that calls addNewDataListener leaks one closure and its retained objects. refreshFromDisk() iterates this ever-growing array and calls every closure including ones from long-dead view models.

Shared/Views/EntryListView.swift:2224 | CRITICAL | MotionManager accelerometer never stopped — no stop() call in any lifecycle hook

  • What: MotionCardView.body calls motionManager.startIfNeeded() in .onAppear (starts CMMotionManager at 30 Hz), but there is no .onDisappear modifier calling motionManager.stop(). The stop() function exists but is never invoked from any view.
  • Impact: Accelerometer runs at 30 Hz continuously for the entire app session once any MotionCardView appears. Constant battery drain regardless of whether the motion-style list is visible.

Shared/Views/SettingsView/LiveActivityPreviewView.swift:325-356 | CRITICAL | ImageRenderer called on background thread — threading violation

  • What: DispatchQueue.global(qos: .userInitiated).async at line 325 calls ImageRenderer(content:).uiImage on a background thread. ImageRenderer is @MainActor and must be used on the main thread only.
  • Impact: Undefined behavior — crashes, blank images, or data corruption. Direct violation of Apple's concurrency contract.

Shared/Services/WatchConnectivityManager.swift:117-124 | CRITICAL | pendingMoods array accessed from multiple threads — data race

  • What: WatchConnectivityManager has no actor isolation. pendingMoods is appended in sendMoodToPhone (called from any context) and read+cleared in activationDidCompleteWith (called on a WCSession background queue). No lock, serial queue, or actor isolation protects concurrent access.
  • Impact: Data race on pendingMoods can corrupt the array — crashes, lost pending moods, or duplicate sends.

Shared/MoodStreakActivity.swift:215-222 | CRITICAL | Infinite loop risk in getStreakData() when calendar.date(byAdding:) returns nil

  • What: while true loop advances checkDate via calendar.date(byAdding: .day, value: -1, to: checkDate) ?? checkDate. If date(byAdding:) returns nil, checkDate does not advance and the loop never breaks.
  • Impact: Infinite loop hangs the main actor indefinitely, freezing the app or widget process.

Shared/Views/CelebrationAnimations.swift:109 | BUG | DispatchQueue.main.asyncAfter holding onComplete closure fires after view dismissal — no cancellation

  • What: onComplete() (saves mood entry to Core Data) is scheduled via asyncAfter. If the user navigates away before the animation duration expires, the callback fires anyway, saving a mood entry.
  • Impact: Ghost entries can be saved to Core Data after the user has navigated away. No cancellation mechanism exists.

Shared/DemoAnimationManager.swift:60-70 | BUG | Orphaned DispatchQueue.main.asyncAfter closures when restartAnimation() called rapidly

  • What: Both startDemoMode() and restartAnimation() schedule asyncAfter with 3-second delay to call beginAnimation(). restartAnimation() invalidates the Timer but cannot cancel the GCD block. Rapid calls queue multiple beginAnimation() invocations.
  • Impact: Multiple Timer instances run simultaneously at 60 Hz, each mutating animationProgress, causing corrupt animation state and battery drain.

Shared/DemoAnimationManager.swift:91-95 | BUG | Timer fires at 60 Hz into Task { @MainActor }, accumulating tasks under load

  • What: Timer callback enqueues Task { @MainActor in self?.updateProgress() } on every tick (60/sec). If the main thread is busy, tasks pile up causing animationProgress to jump discontinuously.
  • Impact: Animation stutters. Up to 60 Task objects per second simultaneously alive, each holding a strong reference to self.

Shared/FeelsApp.swift:92 | BUG | Task.detached(priority: .utility) { @MainActor in ... } runs everything on main thread

  • What: Three heavy DataController operations (refreshFromDisk, removeDuplicates, fillInMissingDates) plus analytics run in a Task.detached tagged @MainActor. The @MainActor annotation forces entire body back onto the main thread — detached provides zero off-main-thread benefit.
  • Impact: Every foreground transition blocks the main thread with Core Data work, causing UI unresponsiveness for users with large datasets.

Shared/FeelsApp.swift:110-117 | WARNING | Fire-and-forget Task.detached blocks pile up on rapid scene transitions

  • What: Multiple Task.detached closures for scheduleBasedOnCurrentTime() and processPendingSideEffects() have no cancellation handle. Rapid foreground transitions queue stacks of these tasks concurrently, each mutating shared state.
  • Impact: Under rapid scene transitions, multiple concurrent mutations to subscription status, Live Activity scheduling, and pending side effects.

Shared/Views/InsightsView/InsightsViewModel.swift:88-113 | PERFORMANCE | withTaskGroup child tasks all @MainActor — run serially, negating concurrency

  • What: All three child tasks in generateAllInsights() are @MainActor-isolated. The main actor is a serial executor, so the tasks run sequentially rather than in parallel.
  • Impact: Three AI inference calls run in series on the main actor, blocking UI for their full combined duration.

Shared/MoodStreakActivity.swift:306 | BUG | scheduleEnd Timer has no [weak self] — inconsistent with scheduleStart

  • What: scheduleEnd(at:) Timer callback captures singletons directly without [weak self]. Inconsistent with scheduleStart which uses [weak self]. If refactored away from singletons, this becomes a retain cycle.
  • Impact: Low current risk (singletons), but fragile and inconsistent capture semantics.

Shared/IAPManager.swift:465-474 | WARNING | deinit cancel of listenForTransactions() Task is dead code — singleton never deinits

  • What: Task is cancelled in deinit, but IAPManager is a singleton and deinit is never called in production. The [weak self] and deinit cancel are dead code creating a misleading code contract.
  • Impact: If IAPManager is ever instantiated non-singleton (e.g., in tests), the task continues iterating Transaction.updates after deallocation, silently calling methods on nil self.

Shared/Views/InsightsView/InsightsViewModel.swift:60-63 | WARNING | generateInsights() spawns unstructured Task{} with no stored handle — multiple concurrent tasks possible

  • What: generateInsights() creates Task{} without storing the handle. Multiple .onAppear or pull-to-refresh calls can spawn concurrent tasks writing to the same @Published arrays.
  • Impact: Flickering loading states; potential for two in-flight LanguageModelSession instances consuming memory.

SOURCE: IAP Auditor (16 findings)

Shared/IAPManager.swift:274 | CRITICAL | Transaction.currentEntitlements never calls transaction.finish()

  • What: The loop iterating currentEntitlements processes transactions but never calls await transaction.finish(). StoreKit 2 requires finish() to be called after processing every transaction.
  • Impact: Transactions remain unfinished in the payment queue indefinitely, re-delivered on subsequent app launches causing repeated processing.

Shared/IAPManager.swift:289 | CRITICAL | try? on subscription.status silently grants subscribed state on StoreKit errors

  • What: let statuses = try? await subscription.status discards StoreKit errors. If the call throws (network offline, StoreKit unavailable), statuses is nil and execution falls through to line 333 which grants .subscribed as a fallback.
  • Impact: A user with expired/cancelled subscription gets full premium access whenever StoreKit is temporarily unavailable. Revenue-loss vulnerability.

Shared/IAPManager.swift:333 | CRITICAL | Fallback unconditionally grants .subscribed when status retrieval fails

  • What: Line 333 grants state = .subscribed(...) whenever product.subscription is nil or subscription.status fails. Any condition preventing subscription type retrieval silently upgrades the user.
  • Impact: StoreKit failures grant premium access with no actual entitlement verification.

Shared/IAPManager.swift:193-202 | CRITICAL | Revoked/expired subscriptions restore .subscribed on next cold launch via cache

  • What: Terminal states (.expired, .revoked) are handled but do NOT clear the cached expiration date. On the next cold launch, checkForActiveSubscription returns false, the terminal guard is skipped (state was reset), and the cache fallback (line 207) grants .subscribed again.
  • Impact: Revocation does not survive a cold relaunch. Users whose subscriptions are revoked (e.g., refund) get premium access back on next launch.

Shared/IAPManager.swift:207-211 | BUG | Cache fallback restores .subscribed even when StoreKit explicitly found no active subscription

  • What: After a live StoreKit check found no active subscription, lines 206-211 restore .subscribed from cache if the cached expiration is in the future. The cache is never cleared when StoreKit explicitly returns expired/revoked.
  • Impact: Users who cancel subscriptions retain full access until cached expiration date, regardless of StoreKit's live result.

Shared/Views/FeelsSubscriptionStoreView.swift:51 | BUG | dismiss() called outside async Task — executes before checkSubscriptionStatus() completes

  • What: dismiss() is called synchronously immediately after spawning Task { @MainActor in ... }, not inside it. The view dismisses before subscription status is confirmed and analytics complete.
  • Impact: UI remains in stale IAP state after purchase; async task continues running against a dismissed view's environment.

Shared/Onboarding/views/OnboardingSubscription.swift:139-142 | BUG | onDismiss unconditionally completes onboarding regardless of purchase outcome

  • What: .sheet(isPresented:onDismiss:) calls completionClosure(onboardingData) in onDismiss whether the user subscribed or cancelled the sheet.
  • Impact: Onboarding completes even when user dismisses paywall without subscribing. Also fires onboardingCompleted analytics twice if user used the skip path first.

Shared/IAPManager.swift:465-473 | WARNING | Transaction.updates finishes transactions but currentEntitlements does not — asymmetric

  • What: listenForTransactions() correctly calls transaction.finish() on all Transaction.updates. But the initial check in checkForActiveSubscription never finishes transactions from currentEntitlements.
  • Impact: Transactions delivered at launch via currentEntitlements will be re-delivered via Transaction.updates, causing double-processing.

Shared/Views/FeelsSubscriptionStoreView.swift:54-55 | WARNING | .pending purchase state tracked as failure in analytics

  • What: case .success(.pending) calls trackPurchaseFailed(error: "pending"). Pending transactions (Ask to Buy, deferred payment) are valid non-failure states.
  • Impact: Purchase funnel metrics miscounted; user receives no pending-approval UI confirmation.

Shared/IAPManager.swift:232-244 | WARNING | restoreCachedSubscriptionState() grants .subscribed with no expiration from a UserDefaults boolean alone

  • What: When hasActiveSubscription is true but no expiration date is stored, grants state = .subscribed(expirationDate: nil, willAutoRenew: false). Relies solely on a UserDefaults boolean.
  • Impact: If UserDefaults is corrupted or not cleaned up after expiration on older app versions, users get unlimited premium access until async check completes. Brief window at cold launch.

Shared/IAPManager.swift:40-44 | WARNING | bypassSubscription debug flag identical in both #if DEBUG and #else blocks — dead code

  • What: Both branches set bypassSubscription = false. The #if DEBUG variant is dead code with no difference from release build.
  • Impact: Developers believe a debug bypass exists but it doesn't; risk of accidentally modifying release value during testing.

Shared/Views/CustomizeView/CustomizeView.swift:693-699 | WARNING | openSubscriptionManagement() failure only print()-logged — no user feedback

  • What: AppStore.showManageSubscriptions(in: windowScene) failure in catch block only calls print(...). No alert or user notification on failure.
  • Impact: Users tapping "Manage Subscription" see nothing happen and receive no explanation if it fails.

Shared/IAPManager.swift:156-162 | WARNING | Throttle skips re-check even in .expired or .revoked state

  • What: 5-minute throttle prevents checkSubscriptionStatus() from re-running even when state is .expired or .revoked. restore() also goes through this throttle.
  • Impact: User who restores purchases within 5-minute window of previous check sees unchanged state until throttle expires.

Shared/FeelsApp.swift:120 | WARNING | Task.detached(priority: .background) calling @MainActor subscription check runs on main thread anyway

  • What: checkSubscriptionStatus() is @MainActor-isolated. Task.detached(priority: .background) immediately hops to the main actor, providing no threading benefit.
  • Impact: Network StoreKit call and state mutation occur on main thread during foreground transitions.

Shared/Views/PurchaseButtonView.swift:127 | WARNING | "Payment Issue" billing label hardcoded English — not localized

  • What: Text("Payment Issue") is a raw string literal while other strings in this file use String(localized:). Shown to users in billing trouble.
  • Impact: Non-English users see "Payment Issue" in English during a critical billing communication.

Shared/IAPManager.swift:139-146 | WARNING | init() Task has no stored cancellation handle — asymmetric cleanup with updateListenerTask

  • What: Task { await checkSubscriptionStatus() } in init() has no handle stored. updateListenerTask is separately cancelled in deinit, but the init task cannot be cancelled.
  • Impact: Inconsistent cleanup semantics; in non-singleton usage (tests), initial check task cannot be cancelled.

SOURCE: Concurrency Auditor (33 findings)

Shared/Services/WatchConnectivityManager.swift:27 | CRITICAL | Data race on pendingMoods array — no actor isolation

  • What: pendingMoods is a plain var array on a class with no actor annotation. It is written in sendMoodToPhone (called from watchOS app context) and both read and written in activationDidCompleteWith (called on the WCSession delegate callback queue, a background thread).
  • Impact: Concurrent read/write of the array constitutes a data race. Under Swift 6 strict concurrency this is a compiler error. In practice this can corrupt the array or produce torn reads.

Shared/Services/WatchConnectivityManager.swift:93-95 | BUG | transferUserInfo called from WCSession error callback on unspecified background thread

  • What: The sendMessage error handler closure at line 93 calls session.transferUserInfo(message) directly. WCSession error callbacks run on an arbitrary background thread. WatchConnectivityManager has no actor isolation.
  • Impact: Potential thread-safety violation calling WCSession API from an indeterminate thread; also silently continues after failure with no reporting to the caller.

Shared/FeelsApp.swift:92 | CRITICAL | Task.detached { @MainActor in } defeats the purpose of detached

  • What: Task.detached(priority: .utility) { @MainActor in ... } creates a detached task but immediately re-isolates the body to @MainActor. The three Core Data calls — refreshFromDisk(), removeDuplicates(), fillInMissingDates() — all execute synchronously on the main actor.
  • Impact: Heavy Core Data work blocks the main thread on every foreground transition, causing UI jank. The detached keyword provides false assurance that this work is off-thread.

Shared/BGTask.swift:25 | CRITICAL | @MainActor background task calls async function without await — task completes before work finishes

  • What: runFillInMissingDatesTask is marked @MainActor and calls MoodLogger.shared.processPendingSideEffects() at line 25 without await. Calling it without await means it begins execution but the caller does not wait for it to finish.
  • Impact: task.setTaskCompleted(success: true) fires before processPendingSideEffects() finishes, incorrectly signaling completion to the OS. Background task work may be partially executed.

Shared/BGTask.swift:14-28 | CRITICAL | @MainActor background task blocks main thread with synchronous Core Data

  • What: runFillInMissingDatesTask is annotated @MainActor, meaning all its work runs on the main thread. DataController.shared.fillInMissingDates() is a synchronous Core Data operation that can scan the entire entries table.
  • Impact: Running fillInMissingDates() on the main thread during a background task blocks the UI thread if the app is active, causing ANR-like behavior.

Shared/BGTask.swift:36 | BUG | Force unwrap of Calendar date result in background scheduler

  • What: runDate = Calendar.current.date(bySettingHour: 0, minute: 1, second: 0, of: runDate!) — force-unwraps the previous date(byAdding:) result. On DST transitions or calendar edge cases, date(byAdding:) can return nil.
  • Impact: Crash in the background task scheduler. Background processing permanently stops scheduling.

Shared/BGTask.swift:42 | BUG | String interpolation syntax error silently suppresses error logging

  • What: print("Could not schedule image fetch: (error)") uses (error) instead of \(error). This is valid Swift — it prints the literal string "(error)" rather than interpolating the actual error value.
  • Impact: All BGTaskScheduler.submit failures are permanently invisible.

Shared/Models/UserDefaultsStore.swift:217 | CRITICAL | Static mutable cache with no synchronization — data race

  • What: private static var cachedOnboardingData: OnboardingData? is a static mutable variable with no actor isolation and no locking. Read and written from @MainActor contexts and background tasks.
  • Impact: Concurrent reads/writes to this static var constitute a Swift 6 data race. The cache could be read in a partially-written state, returning nil or stale OnboardingData.

Shared/Random.swift:174 | CRITICAL | Static mutable dictionary textToImageCache with no thread safety

  • What: private static var textToImageCache = [String: UIImage]() is accessed from textToImage() without any synchronization. Widget rendering, background tasks, and main-thread rendering all can call this simultaneously.
  • Impact: Concurrent dictionary read/write is a data race. Under Swift 6 this is a compiler error. In practice it can corrupt the dictionary, producing crashes or wrong images.

Shared/Random.swift:48,68 | CRITICAL | Static mutable caches existingWeekdayName and existingDayFormat with no thread safety

  • What: Two static var dictionaries are read/written from weekdayName(fromDate:) and dayFormat(fromDate:) with no actor isolation or synchronization. Called from widget rendering (background), Core Data background operations, and main thread UI.
  • Impact: Concurrent dictionary mutations are data races.

Shared/DemoAnimationManager.swift:60-71 | BUG | Uncancellable DispatchQueue.main.asyncAfter causes stacked timer creation

  • What: restartAnimation() calls animationTimer?.invalidate() then DispatchQueue.main.asyncAfter(deadline: .now() + startDelay) { self.beginAnimation() }. The GCD block cannot be cancelled. Multiple timers created and run simultaneously.
  • Impact: Multiple concurrent 60fps timers driving the same animationProgress state cause undefined animation behavior and CPU waste.

Shared/MoodStreakActivity.swift:215-223 | CRITICAL | Infinite loop when Calendar.date(byAdding:) returns nil

  • What: while true loop with ?? checkDate fallback — if date(byAdding: .day, value: -1, to: checkDate) returns nil, checkDate never changes and the loop never terminates.
  • Impact: Infinite loop on @MainActor permanently freezes the UI.

Shared/Persisence/DataControllerGET.swift:74-82 | CRITICAL | Infinite loop when date(byAdding:) returns nil in calculateStreak

  • What: Same while true with ?? checkDate fallback in calculateStreak(from:). On DST edge case, checkDate doesn't advance.
  • Impact: Main thread freeze on @MainActor DataController. Watchdog terminates the app.

Shared/AppShortcuts.swift:128-140 | CRITICAL | Unbounded while true loop on @MainActor with Core Data fetch per iteration

  • What: calculateStreak() runs a while true loop on @MainActor, fetching a Core Data entry for each past day. For a user with a 3-year streak, this performs 1,095 sequential Core Data round-trips synchronously on the main thread.
  • Impact: Main thread blocked for potentially hundreds of milliseconds to seconds. App appears frozen while Siri shortcut resolves.

Shared/DataController.swift:15 | BUG | @MainActor singleton may be initialized off-actor on first access

  • What: static let shared = DataController() on a @MainActor final class. Swift lazily initializes static properties on first access. If accessed before the main actor is running (e.g., from a widget extension or background thread), the @MainActor initializer is called off-actor.
  • Impact: SwiftData ModelContainer and ModelContext created on a non-main thread — undefined behavior.

Shared/Views/InsightsView/InsightsViewModel.swift:88-113 | PERFORMANCE | withTaskGroup child tasks all @MainActor — no real concurrency

  • What: All three group.addTask { @MainActor in ... } closures are isolated to @MainActor. They execute serially despite using a task group.
  • Impact: Three sequential AI insight generation calls on the main actor. Each can take seconds; UI is blocked waiting for all three.

Shared/Views/InsightsView/InsightsViewModel.swift:60-63 | BUG | Concurrent generateInsights() calls not guarded — interleaved @Published mutations

  • What: generateInsights() launches Task { await generateAllInsights() } with no guard. Multiple concurrent tasks independently overwrite @Published arrays.
  • Impact: Race between two tasks writing the same @Published property. Final displayed insights are non-deterministic.

Shared/Analytics.swift:614-618 | WARNING | Swift 6 violation — calling @MainActor method from non-isolated ViewModifier

  • What: ScreenTrackingModifier.body(content:) calls AnalyticsManager.shared.trackScreen(...) inside .onAppear. Under Swift 6, calling a @MainActor function from a non-isolated synchronous context is a compile error.
  • Impact: Swift 6 concurrency violation. Will be a hard error once strict concurrency is enforced.

Shared/Services/PhotoManager.swift:104-134 | PERFORMANCE | Synchronous disk I/O on @MainActor-bound class

  • What: loadPhoto(id:) and loadThumbnail(id:) call Data(contentsOf: fullURL) — synchronous file I/O — on the main actor.
  • Impact: Reading large JPEG files from disk on the main thread causes dropped frames and UI jank during list scrolling.

Shared/Views/SettingsView/LiveActivityPreviewView.swift:325-356 | CRITICAL | ImageRenderer used on background DispatchQueue.global thread

  • What: DispatchQueue.global(qos: .userInitiated).async { let renderer = ImageRenderer(...); renderer.uiImage }. ImageRenderer is @MainActor-isolated per Apple documentation.
  • Impact: Swift 6 data race. Produces blank images, memory corruption, or crashes.

Shared/Services/SharedMoodIntent.swift:93-121 | CRITICAL | New ModelContainer created per widget intent invocation against shared SQLite store

  • What: WidgetMoodSaver.save(mood:date:) creates a fresh ModelContainer on every call. Multiple ModelContainer writers to the same SQLite file can produce locked errors or data corruption. The delete-then-insert sequence can permanently lose user data if the second save fails.
  • Impact: Data corruption or permanent mood entry loss on concurrent intent + main app writes.

Shared/Views/SharingTemplates/LongestStreakTemplate.swift:46-53 | CRITICAL | Non-isolated init directly calls @MainActor-annotated configureData()

  • What: LongestStreakTemplate.init is not @MainActor-isolated but calls self.configureData(fakeData:mood:) which is @MainActor. Calling a @MainActor-isolated function synchronously from a non-isolated context is a Swift 6 concurrency violation.
  • Impact: Swift 6 will produce a compile error. The data load also silently has no effect since it writes to @State vars at init time.

Shared/Views/Photos/PhotoPickerView.swift:151 | BUG | @State mutation from non-@MainActor async context

  • What: loadImage(from:) is declared private func ... async with no @MainActor annotation. Inside it, isProcessing = true mutates a @State property off the main actor.
  • Impact: Swift 6 data race: writing @State off the main actor produces undefined SwiftUI behavior.

Shared/Views/SettingsView/SettingsView.swift:831-843,912-941 | BUG | Task {} inside Binding.set closure mutates @EnvironmentObject without @MainActor

  • What: Task { } closures inside Binding set closures mutate @Published properties of @EnvironmentObject ObservableObjects without explicit @MainActor annotation.
  • Impact: Under Swift 6, setting @Published properties of ObservableObject on a non-main actor is a data race.

Shared/FeelsApp.swift:30 | BUG | Force cast of BGTask in scheduler callback — crashes if wrong type delivered

  • What: BGTask.runFillInMissingDatesTask(task: task as! BGProcessingTask) — force cast inside BGTaskScheduler callback. If scheduler delivers a different task type, crashes with no recovery.
  • Impact: App crashes in the background task handler; system may reduce background execution privileges.

Shared/FeelsApp.swift:110-117 | BUG | Fire-and-forget Task.detached blocks pile up on rapid scene transitions

  • What: Multiple Task.detached closures for scheduleBasedOnCurrentTime() and processPendingSideEffects() have no cancellation handle. Rapid foreground transitions queue concurrent tasks, each mutating shared state.
  • Impact: Multiple concurrent mutations to subscription status, Live Activity scheduling, and pending side effects.

Shared/Views/SettingsView/LiveActivityPreviewView.swift:203-212 | WARNING | Timer callback mutates @State without actor guarantee

  • What: Timer.scheduledTimer callback directly sets @State properties. While timers on the main run loop fire on the main thread, there is no enforcement that startAnimation() is only ever called on the main thread.
  • Impact: If called from a non-main context, mutates @State off the main actor. SwiftUI purple thread-checker warning.

Shared/IAPManager.swift:465-474 | WARNING | Task.detached { [weak self] } on a singleton — misleading ownership semantics

  • What: listenForTransactions() returns Task.detached { [weak self] in ... }. On a singleton, [weak self] never becomes nil. The deinit { updateListenerTask?.cancel() } is also dead code. Future code added directly in the loop body without await self?. would be a data race.
  • Impact: Misleading code contract; maintenance hazard.

Shared/AppDelegate.swift:36-40 | WARNING | @preconcurrency suppresses concurrency violations in UNUserNotificationCenter delegate

  • What: Extension annotated @preconcurrency to silence Swift concurrency warnings. Under Swift 6, @preconcurrency suppresses the violation rather than fixing it.
  • Impact: Hidden concurrency violations in notification delegate callbacks.

Shared/Services/MoodStreakActivity.swift:306-316 | WARNING | scheduleStart Timer guard let self is silent no-op on dealloc — no fallback

  • What: If self is nil when timer fires, Live Activity start silently never happens with no fallback or logging.
  • Impact: Live Activities may silently fail to start after scheduler recreation.

Shared/MoodStreakActivity.swift:324-329 | WARNING | scheduleEnd Timer captures singletons without [weak self]

  • What: End timer closure captures singletons by strong reference; timer never cancelled on deinit (singleton never deallocates).
  • Impact: Leaked timer. If singleton semantics change, this becomes a retain cycle.

Shared/Services/MoodLogger.swift:113 | WARNING | getData(startDate:endDate:includedDays:) called with empty [] for includedDays — behavior undefined

  • What: Empty includedDays array semantics unknown — could mean "no days" rather than "all days". If it means "no days", processPendingSideEffects() never catches up on missed side effects.
  • Impact: Widget or watch-logged moods may never have side effects (Live Activity, HealthKit, streak) applied.

Shared/Persisence/DataControllerGET.swift:18, Shared/Persisence/DataControllerDELETE.swift:49, Shared/Services/ExtensionDataProvider.swift:113 | BUG | Off-by-one inclusive upper bound <= includes entries from next day's midnight

  • What: Predicates use entry.forDate <= endDate where endDate is midnight of the next day. Entries logged exactly at midnight are included in the wrong day's query.
  • Impact: Entries logged at midnight appear in wrong day's data across calendar views, month summaries, and streak calculations.

Shared/Views/SharingTemplates/*.swift (7 files) | WARNING | var image: UIImage calling ImageRenderer-based asImage() without @MainActor annotation

  • What: Seven sharing template types expose var image: UIImage as a plain computed property calling someView.asImage(size:). If asImage() uses ImageRenderer (which is @MainActor-isolated), this is a Swift 6 actor isolation violation.
  • Impact: Compile-time error under strict concurrency. At runtime currently safe since called from button actions on main thread.

SOURCE: SwiftUI Performance Auditor (33 findings)

Shared/Onboarding/views/OnboardingTime.swift:13-17 | PERFORMANCE | DateFormatter computed property allocated every body re-render at 60fps during date picker interaction

  • What: var formatter: DateFormatter { let dateFormatter = DateFormatter(); ... return dateFormatter } is a computed property accessed directly in body. The DatePicker bound to $onboardingData.date updates on every wheel scroll tick, triggering a body re-render every frame.
  • Impact: During date-picker interaction, formatter reallocated ~60 times/second wasting CPU on repeated ICU initializations.

Shared/Onboarding/views/OnboardingWrapup.swift:15-19 | PERFORMANCE | DateFormatter computed property allocated every body re-render

  • What: Same pattern as OnboardingTime. var formatter: DateFormatter is a computed property creating a new instance each access.
  • Impact: Repeated expensive allocations on every render.

FeelsWidget2/FeelsTimelineWidget.swift:73-83 | PERFORMANCE | DateFormatter created as computed property on widget views — reallocated on every render pass

  • What: private var dayFormatter: DateFormatter and private var dateFormatter: DateFormatter are computed properties constructing new DateFormatter instances. Repeated in SmallWidgetView, MediumWidgetView, and LargeWidgetView.
  • Impact: Widget rendering is memory-constrained. Multiple DateFormatter objects allocated and immediately discarded per timeline refresh.

FeelsWidget2/FeelsTimelineWidget.swift:165-170 | PERFORMANCE | headerDateRange creates another DateFormatter inside a computed property called in body

  • What: private var headerDateRange: String creates a fresh DateFormatter inline per call. Third DateFormatter allocation per MediumWidgetView render.
  • Impact: Compounding allocations in memory-limited widget extension.

FeelsWidget2/FeelsVoteWidget.swift:64-76 | PERFORMANCE | votingDateString computed property creates two DateFormatter instances per call

  • What: Constructs dayFormatter and dateFormatter inside the function body. Accessed directly from body.
  • Impact: Two DateFormatter allocations per body evaluation including locale-sensitive ICU initialization.

Shared/Views/DayView/DayView.swift:99-103 | PERFORMANCE | sortedGroupedData recomputes expensively on every body pass with no caching

  • What: private var sortedGroupedData calls .sorted { $0.key > $1.key } twice and constructs a new array on every body evaluation. Unlike MonthView which caches in @State, DayView re-sorts on every render.
  • Impact: O(n log n) work on every render for all users with multiple years of data.

Shared/Views/MonthView/MonthView.swift:173-175 | PERFORMANCE | displayData.flatMap runs in body every render — recreates full flattened month array unconditionally

  • What: let allMonths = displayData.flatMap { ... } runs on every body evaluation, creating a new array even on trivial state changes.
  • Impact: O(n) allocation for users with several years of data on every render including trivial changes.

Shared/Views/MonthView/MonthView.swift:176 | BUG | ForEach uses id: \.element.month — non-unique across years, causes diffing collisions

  • What: ForEach(Array(allMonths.enumerated()), id: \.element.month) uses integer month number (112) as identity. January 2023 and January 2024 both have .month == 1.
  • Impact: SwiftUI may skip rendering new month cards, animate between wrong cells, or fail to insert/remove months on data changes for users with more than one year of data.

Shared/Views/MonthView/MonthView.swift:410 | PERFORMANCE | @ObservedObject private var demoManager = DemoAnimationManager.shared inside MonthCard — 60fps singleton updates fan to all cards

  • What: Every MonthCard holds an @ObservedObject reference to the same singleton. When demoManager.animationProgress publishes at 60 fps during demo mode, all MonthCard views receive objectWillChange, including off-screen cards.
  • Impact: Dozens of simultaneous subscription notifications per frame during demo animation.

Shared/Views/MonthView/MonthView.swift:431-443 | PERFORMANCE | animatedMetrics computed property calls demoManager.visiblePercentageForMonth on every body pass during animation

  • What: Reconstructs full [MoodMetrics] array via .map on every evaluation. During demo animation at 60 fps, runs for every card in the list on every frame.
  • Impact: O(n_months × 5_moods × 60fps) object allocations per second during demo playback.

Shared/Views/MonthView/MonthView.swift:661 | BUG | cachedMetrics only recalculated when empty — stale metrics after entry edits

  • What: if cachedMetrics.isEmpty { cachedMetrics = ... } in .onAppear. If user edits a mood entry, entries changes but cachedMetrics is non-empty so never recalculates. Equatable conformance checks only count, not content.
  • Impact: After editing a mood entry, MonthCard shows old mood distribution stats. Share sheet also uses cachedMetrics, producing incorrect shared images.

Shared/Views/YearView/YearView.swift:334-342 | BUG | YearCard.Equatable compares yearEntries.count only — stale cached metrics after data edits

  • What: YearCard.static func == checks only count. cachedMetrics calculated only when empty. If entry moods change without changing count, YearCard never re-renders.
  • Impact: Year statistics and share images show incorrect mood distribution after any edit.

Shared/Views/YearView/YearView.swift:174-178 | PERFORMANCE | GeometryReader as preference-key scroll offset tracker — fires on every scroll pixel

  • What: GeometryReader embedded in scroll content .background to measure scroll offset and emit via preference key. Fires onPreferenceChange on every single scroll position change. Same pattern in MonthView.
  • Impact: Every scroll position change triggers a preference update at 60+ fps → main thread callback → potential animation state mutation.

Shared/Views/YearView/YearViewModel.swift:36-38 | PERFORMANCE | O(n log n) sort to find minimum date when min(by:) is O(n)

  • What: filteredEntries.sorted(by: { $0.forDate < $1.forDate }).first?.forDate sorts entire array to get earliest date.
  • Impact: Unnecessary sort on every updateData() call. min(by:) achieves same result in O(n).

Shared/Views/YearView/YearViewModel.swift:48-51 | PERFORMANCE | data.removeAll() and entriesByYear.removeAll() trigger two separate SwiftUI layout passes with empty state

  • What: In filterEntries, two separate removeAll() calls each publish objectWillChange before new data is assigned.
  • Impact: View briefly renders empty-data state (showing EmptyHomeView) twice on every year data refresh.

Shared/Views/CelebrationAnimations.swift:311 | PERFORMANCE | Double.random(in:) called inside body inside ForEach — non-deterministic opacity on every render

  • What: ShatterReformAnimation.body applies .fill(mood.color.opacity(Double.random(in: 0.5...1.0))) inside ForEach. Generates new random opacity on every body evaluation.
  • Impact: Shard colors re-randomize on every state change causing visible flickering between animation phases.

Shared/Views/EntryListView.swift:139,146 | PERFORMANCE | classicStyle bypasses DateFormattingCache — repeated ICU operations on every body evaluation

  • What: classicStyle calls Random.weekdayName and Random.dayFormat directly, bypassing DateFormattingCache.shared. Same bypass in microStyle and MotionCardView.
  • Impact: classicStyle is default and most common. With 365+ cells in the list, cache misses cause redundant date formatting on every redraw.

Shared/Views/EntryListView.swift:2249 | PERFORMANCE | CMMotion callback calls withAnimation(.interactiveSpring(...)) on every accelerometer update at 30 Hz

  • What: The motionManager.startDeviceMotionUpdates(to: .main) callback wraps every accelerometer reading in withAnimation(.interactiveSpring(...)), re-enqueuing an animation transaction at 30 Hz.
  • Impact: 30 animation transactions per second on the main thread for all visible MotionCardView cells simultaneously.

Shared/Views/FeelsSubscriptionStoreView.swift:165-177 | PERFORMANCE | Multiple withAnimation(.repeatForever(...)) started in .onAppear — cannot cancel on dismiss

  • What: Every theme marketing view (12 total) starts one or more repeatForever animations in .onAppear. .onDisappear resets state values but cannot cancel in-flight repeating animation transactions.
  • Impact: Rapid open/close cycles stack animation transactions, causing visual glitches and CPU overhead.

Shared/Views/LockScreenView.swift:1121-1128 | PERFORMANCE | CGFloat.random(in:) called inside body for cloud sizes — re-randomizes on every state change

  • What: ForecastLockBackground uses CGFloat.random(in: 40...80) inline inside the view body for cloud element sizes and blur values. Not captured in @State or onAppear.
  • Impact: Cloud sizes change on every body re-render, producing visual instability during authentication flow.

FeelsWidget2/FeelsTimelineWidget.swift:87-92 | PERFORMANCE | TimeLineCreator.createViews and UserDefaultsStore.moodTintable() called synchronously in widget init

  • What: Widget view init calls TimeLineCreator.createViews(daysBack:) and UserDefaultsStore.moodTintable() synchronously across all three widget size classes.
  • Impact: Blocking synchronous I/O on WidgetKit's render thread. Contention with main app's ModelContainer can cause widget render deadline timeout.

Shared/Views/DayView/DayView.swift:89 | PERFORMANCE | UserDefaultsStore.getOnboarding() called inside headerView computed property on every body pass

  • What: headerView calls ShowBasedOnVoteLogics.isMissingCurrentVote(onboardingData: UserDefaultsStore.getOnboarding()) on every body pass. Decode path reachable on cache miss.
  • Impact: Every body re-render of DayView calls into UserDefaultsStore.getOnboarding(). Not thread-safe.

Shared/Views/CustomizeView/CustomizeView.swift:295 | WARNING | Hidden Text hack to force re-renders — fragile, may be optimized away by future OS

  • What: Text(String(customMoodTintUpdateNumber)).hidden().frame(height: 0) inserted as workaround to trigger re-renders. SwiftUI anti-pattern.
  • Impact: Future OS optimizations of hidden views may silently break this, causing tint changes to stop reflecting in picker UI.

Shared/Views/MonthView/MonthView.swift:246-259 | PERFORMANCE | AnyView type erasure for conditional .mask modifier — forces full re-creation on paywall state change

  • What: .mask modifier uses (condition) ? AnyView(LinearGradient(...)) : AnyView(Color.black). SwiftUI cannot track structural identity through AnyView. Same pattern in YearView at lines 184-195.
  • Impact: Entire ScrollView content re-created from scratch on paywall state changes instead of targeted update.

Shared/Views/SharingListView.swift:51 | PERFORMANCE | Heavy use of AnyView for all sharable items — disables SwiftUI structural diffing for entire sharing list

  • What: WrappedSharable structs use AnyView for .preview and .destination. ForEach over AnyView items means no structural identity comparison.
  • Impact: Any state change causes all sharing item previews to be fully re-created.

Shared/Models/MoodTintable.swift:112-143 | PERFORMANCE | UserDefaultsStore.getCustomMoodTint() called up to 10 times per render for custom tint views

  • What: CustomMoodTint.color(forMood:) and CustomMoodTint.secondary(forMood:) each call UserDefaultsStore.getCustomMoodTint() per invocation. A full month card calls it 10 times.
  • Impact: For custom tint users, every cell render invokes multiple JSON decode cycles. 365 cells = thousands of UserDefaults reads before first frame.

Shared/Views/DemoAnimationManager.swift:91-95 | PERFORMANCE | Timer.scheduledTimer at 1/60s spawns new Task { @MainActor in } per tick — tasks queue faster than consumed

  • What: Timer fires every 1/60 second; each fire wraps updateProgress() in Task { @MainActor in ... }. Under load, Tasks queue up and multiple updateProgress() calls fire for the same frame.
  • Impact: Animation progress applied multiple times per visual frame causing stutters, jumps, or skips.

FeelsWidget2/FeelsVoteWidget.swift:55-176 | PERFORMANCE | moodTint and moodImages computed props each call UserDefaultsStore on every body access

  • What: private var moodTint and private var moodImages are computed properties triggering UserDefaults reads on every access. Called multiple times within body.
  • Impact: 46 UserDefaults reads plus potential JSON decoding per widget render cycle. Increases likelihood of exceeding WidgetKit render budget.

FeelsWidget2/FeelsIconWidget.swift:50-58 | PERFORMANCE | customWidget computed property called twice in body — reads UserDefaults twice per render

  • What: customWidget reads UserDefaults each access; called twice in body, doubling I/O per render.
  • Impact: Doubles UserDefaults I/O per render in the memory-constrained widget extension.

Shared/MoodLogger.swift:134,141 | PERFORMANCE | ISO8601DateFormatter() instantiated fresh on every call

  • What: MoodLogger creates a new ISO8601DateFormatter() at lines 134 and 141 inside date key functions. Called every time the user logs a mood.
  • Impact: ISO8601DateFormatter has similar initialization cost to DateFormatter. Should be static let properties.

Shared/Views/ExportView.swift:177-178 | PERFORMANCE | DateFormatter created inside dateRangeText computed property on every call

  • What: private var dateRangeText: String creates DateFormatter inline. Called from body during export progress updates.
  • Impact: Each ExportView body evaluation allocates and discards a DateFormatter.

Shared/Views/AddMoodHeaderView.swift:343-344 | WARNING | repeatForever animation started in .onAppear.onDisappear resets value but cannot cancel in-flight repeat

  • What: OrbitVotingView.onAppear starts repeatForever animation on centerPulse. .onDisappear sets centerPulse = 1.0 nominally stopping it. Rapid navigation can cause animation to restart from intermediate state. Same pattern in NeonVotingView lines 553-558.
  • Impact: Rapid navigation to/from day view causes orbit pulse to visually jump or stutter.

FeelsWidget2/FeelsTimelineWidget.swift:236 | PERFORMANCE | Date(timeIntervalSince1970: 0) in LargeWidgetView fetches ALL entries since Unix epoch

  • What: Large widget fetches entries with startDate: Date(timeIntervalSince1970: 0) — no lower-bound date filter, loading every mood entry the user has ever logged into the widget extension's memory.
  • Impact: Long-term users with years of data can have thousands of MoodEntryModel objects loaded into the widget extension — a severely memory-constrained process. Can be killed by jetsam, resulting in blank widget.

Shared/Views/MonthView/MonthView.swift:342-344,362-365 | WARNING | Two separate .onAppear modifiers on same view root — ordering not guaranteed

  • What: Two sequential .onAppear modifiers: one fires analytics, one populates cachedSortedData. SwiftUI ordering of multiple .onAppear handlers is implementation-defined.
  • Impact: If Apple changes execution order, analytics could fire before data is available.

SOURCE: Security Auditor (33 findings)

Shared/Analytics.swift:24 | CRITICAL | Hardcoded PostHog API key committed to source control

  • What: private static let apiKey = "phc_3GsB3oqNft8Ykg2bJfE9MaJktzLAwr2EPMXQgwEFzAs" is a live production analytics key stored as a source-level string literal, permanently captured in git history.
  • Impact: Any party with the key can inject events into PostHog, poison analytics dashboards, and potentially enumerate device/user data via the PostHog API.

Shared/GoogleService-Info.plist:10 | CRITICAL | Firebase/Google API key and project credentials in source control

  • What: AIzaSyDRIs9buGGPWtECfyhCgFsNvYzvmQnhDr0 (Firebase API key), CLIENT_ID, GCM_SENDER_ID, and GOOGLE_APP_ID committed in a checked-in plist. Firebase appears to be removed from the codebase but the credential file was not deleted.
  • Impact: Active Google API key permanently in git history. Allows unauthenticated Firebase Auth, Storage, and GCM calls. Should be revoked in Firebase console and deleted from repository.

Shared/Analytics.swift:67-76 | CRITICAL | Session replay captures UI screenshots in production with images unmasked and enabled by default

  • What: config.sessionReplayConfig.screenshotMode = true, config.sessionReplayConfig.maskAllImages = false, and config.captureNetworkTelemetry = true set in PostHog configuration. Session replay enabled by default for new users (opt-out required, not opt-in).
  • Impact: PostHog screenshot-mode session replay can capture full screen contents — including mood entries, journal notes, and photos — without user consent presented upfront. Potential GDPR/CCPA violation for sensitive mental health data.

Shared/Services/BiometricAuthManager.swift:17 | CRITICAL | isUnlocked defaults to true — locked content briefly visible before auth runs

  • What: @Published var isUnlocked: Bool = true. Views gated on isUnlocked are rendered and visible before the biometric prompt appears.
  • Impact: On app foreground, mood data is momentarily visible before Face ID/Touch ID challenge completes, even with lock enabled.

Shared/Services/BiometricAuthManager.swift:172-175 | CRITICAL | disableLock() requires no re-authentication

  • What: disableLock() directly sets isLockEnabled = false and isUnlocked = true with no biometric or passcode challenge.
  • Impact: An attacker with brief physical access to an unlocked app can permanently disable privacy lock with a single tap in Settings.

Shared/Persisence/ExtensionDataProvider.swift:199-217 | CRITICAL | Non-atomic delete-then-insert can permanently delete user mood data

  • What: Existing entries are deleted and try? context.save() is called (line 205). If this succeeds but the subsequent insert save fails (line 217), the original entries are permanently deleted with no recovery path.
  • Impact: User loses all mood entries for a given date — the delete was persisted but the insert was not. No error surface, no retry, no rollback.

Shared/Persisence/DataControllerADD.swift:13-20 | CRITICAL | Non-atomic delete-save-insert in main app path — same data loss risk

  • What: add() deletes all entries for a date, calls try? modelContext.save(), then inserts and calls saveAndRunDataListeners(). If the final save fails, deleted entries cannot be recovered.
  • Impact: User mood data silently lost on save failure during mood logging.

Shared/Random.swift:30,32 | CRITICAL | Force-unwrap on UserDefaults(suiteName:) for all app group defaults

  • What: GroupUserDefaults.groupDefaults force-unwraps UserDefaults(suiteName:)!. Called on almost every UI render cycle and data operation.
  • Impact: App crashes at startup and on every subsequent access if app group entitlement is misconfigured, revoked, or unavailable.

Shared/Views/SettingsView/SettingsTabView.swift:61-66 | CRITICAL | CustomizeContentView missing required @EnvironmentObject injections — guaranteed crash

  • What: CustomizeContentView() used inside SettingsTabView without .environmentObject(authManager) or .environmentObject(iapManager). Child views declare @EnvironmentObject var iapManager: IAPManager.
  • Impact: App crashes at runtime with fatal "No ObservableObject of type IAPManager found" when user navigates to Customize tab from Settings.

Shared/Analytics.swift:160-163 | WARNING | Privacy-sensitive settings transmitted as super-properties to PostHog on every event

  • What: privacy_lock_enabled and healthkit_enabled are registered as super-properties attached to every analytics event, along with theme, icon_pack, voting_layout, day_view_style, mood_shape, and personality_pack values.
  • Impact: Device configuration and security-feature state attached to every PostHog capture event. May conflict with App Store privacy disclosures if not declared.

Shared/Analytics.swift:255-262 | BUG | Race condition in analytics opt-out: SDK can be left opted-out while flag shows opted-in

  • What: optIn() first writes false to UserDefaults, then calls PostHogSDK.shared.optIn(). If the app crashes between these two lines, inconsistent state persists until next explicit toggle.
  • Impact: User believes analytics are disabled but events are being dropped, or vice versa.

Shared/Services/BiometricAuthManager.swift:108-110 | WARNING | Cancelled biometric prompt silently escalates to passcode without user consent

  • What: When Face ID throws (user taps Cancel), the catch block unconditionally calls authenticateWithPasscode() if canUseDevicePasscode is true.
  • Impact: A user who cancels Face ID expecting to deny access is immediately presented with a passcode prompt — non-standard iOS authentication UX.

Shared/IAPManager.swift:206-212 | WARNING | Cached subscription expiration unconditionally trusted offline — no cryptographic verification

  • What: cachedSubscriptionExpiration is a plain Date in GroupUserDefaults. If the stored date is in the future, state = .subscribed(...) is set without re-verifying with StoreKit.
  • Impact: GroupUserDefaults accessible to any app in the same app group. Modification of the expiration date grants permanent subscription access.

Shared/IAPManager.swift:233-244 | WARNING | restoreCachedSubscriptionState() grants .subscribed with no expiration from UserDefaults boolean alone

  • What: If hasActive is true and cachedExpiration is nil, grants state = .subscribed(expirationDate: nil, willAutoRenew: false) unconditionally.
  • Impact: nil expiration means the widget always shows premium UI regardless of actual entitlement.

Shared/Models/UserDefaultsStore.swift:217,221-234 | WARNING | try? silently discards all Codable decode errors for persisted user settings

  • What: JSON decode failures for onboarding data, custom widgets, and custom mood tints all swallowed with try?. Functions return default values with no indication stored data was discarded.
  • Impact: Data corruption in UserDefaults silently loses user customization state with no error message.

Shared/Models/UserDefaultsStore.swift:217 | WARNING | Static cache cachedOnboardingData has no thread synchronization

  • What: private static var cachedOnboardingData: OnboardingData? is a static mutable variable accessed from both main thread and background tasks.
  • Impact: Concurrent reads/writes constitute a data race. Can corrupt cache or read partially-initialized state.

Shared/Random.swift:48,68,174 | WARNING | Mutable static dictionaries have no thread-safety protection

  • What: static var existingWeekdayName, static var existingDayFormat, and static var textToImageCache are mutable static dictionaries accessible to any context including widget extension background rendering.
  • Impact: Concurrent access can corrupt dictionaries, producing wrong weekday names, wrong day formats, or cached images mapped to incorrect keys.

Shared/Random.swift:65 | BUG | monthName(fromMonthInt:) crashes when fromMonthInt is 0 or out of range

  • What: monthSymbols[fromMonthInt-1] with no bounds check. Passing 0 produces [-1] — fatal index out of bounds crash.
  • Impact: Any code path that constructs a month integer from raw/corrupted data will crash the app.

Shared/Services/WatchConnectivityManager.swift:117-124 | WARNING | pendingMoods array accessed from multiple threads without synchronization

  • What: Written from Watch main thread and read/mutated from WCSession delegate background queue with no synchronization.
  • Impact: Concurrent access can corrupt the pendingMoods array, causing mood entries to be dropped, duplicated, or invalid indices accessed.

Shared/Analytics.swift:67 | WARNING | captureElementInteractions = true auto-captures all UI interaction events

  • What: PostHog auto-captures taps, element names, and screen transitions without explicit event definitions.
  • Impact: Button labels, navigation paths, and element identifiers sent to PostHog automatically. If any label contains user-generated content, it could be inadvertently captured.

Shared/Analytics.swift:552 | BUG | Force-unwrap on Optional after nil check in event payload construction

  • What: error != nil ? ["error": error!] : nil — force-unwrap after explicit nil check is an unsafe pattern. Future refactors could move the force-unwrap outside the conditional.
  • Impact: Crash if pattern is modified without understanding the guard structure.

Shared/FeelsApp.swift:30 | BUG | Force cast task as! BGProcessingTask inside BGTaskScheduler handler

  • What: If system delivers a different task type, crashes at runtime with no recovery.
  • Impact: App crash during background processing.

Shared/Services/HealthService.swift:79-80 | BUG | isAuthorized set to true regardless of whether HealthKit permission was granted

  • What: After try await healthStore.requestAuthorization(toShare:read:) returns without throwing, isAuthorized = true and isEnabled = true are set. HealthKit does not throw if user denies permissions.
  • Impact: App displays "HealthKit connected" and attempts HealthKit saves even when user denied permissions. HealthKit writes silently fail while UI shows sync as enabled.

Shared/Services/BiometricAuthManager.swift:105,129,149,167 | WARNING | Biometric auth errors logged via print() not structured logger

  • What: All error paths in auth functions use print(...) instead of AppLogger.biometrics. Security-relevant error descriptions visible in console in production builds.
  • Impact: LAError codes visible in crash logs shared with third parties.

Shared/Services/BiometricAuthManager.swift (class level) | WARNING | isLockEnabled stored in GroupUserDefaults accessible to widget and watch extensions

  • What: Lock state is in the shared app group suite, readable and writable by widget and watch extensions.
  • Impact: A compromised widget/watch extension can read whether privacy lock is enabled and write false to disable it, bypassing biometric lock without triggering main app audit trail.

Shared/IAPManager.swift:70 | WARNING | firstLaunchDate returns Date() (now) when key is absent — trial calculation uses wrong time

  • What: GroupUserDefaults.groupDefaults.object(forKey: .firstLaunchDate) as? Date ?? Date(). If key missing (cleared or fresh install), trial window calculated from right now.
  • Impact: Reinstalling or clearing UserDefaults can permanently reset the trial period, granting a new 30-day trial on demand.

Shared/Persisence/DataControllerDELETE.swift:22-40 | WARNING | deleteLast and deleteRandomFromLast are production-accessible with no authorization gate

  • What: deleteRandomFromLast(numberOfEntries:) randomly deletes user mood entries. Both functions are protocol-level requirements available in production builds with no #if DEBUG guard.
  • Impact: Any code path that accidentally calls deleteRandomFromLast irreversibly destroys user mood history with no recovery.

Shared/Views/SettingsView/SettingsView.swift:1158 | BUG | Force-unwrap URL from user-controlled path component

  • What: URL(string: "shareddocuments://\(url.path)")! where url.path is a runtime-generated file URL path that can contain spaces or special characters.
  • Impact: App crashes when export path contains any character that invalidates the URL.

Shared/Views/CustomizeView/CustomizeView.swift:548-556 | WARNING | NSFW personality pack age gate is commented out

  • What: The conditional block gating access to the "Rude" personality pack behind an over-18 confirmation has been commented out. All personality packs accessible without any age check.
  • Impact: Users of all ages can select adult-language notification personality packs. App Store compliance issue if marketed to minors.

Shared/Onboarding/views/OnboardingSubscription.swift:139-145 | BUG | Onboarding completion fires unconditionally on subscription sheet dismiss

  • What: onDismiss handler calls completionClosure(onboardingData) regardless of whether user subscribed. onboardingCompleted analytics fires on both skip and dismiss paths, producing double-fire.
  • Impact: Onboarding marked complete even if user closed paywall without subscribing. Corrupts onboarding funnel metrics.

No .xcprivacy file found in project | CRITICAL | Missing PrivacyInfo.xcprivacy manifest — App Store compliance violation

  • What: There is no .xcprivacy file anywhere in the project tree. The app uses PostHog session replay (screenshots), CoreMotion accelerometer, HealthKit, photos, notifications, and UserDefaults — all requiring privacy manifest declarations per App Store requirements effective May 2024.
  • Impact: Apple will reject app updates submitted without a privacy manifest declaring all required reason APIs and third-party SDKs (PostHog). App is out of compliance with current App Store review policy.

Shared/Analytics.swift:36-50 | WARNING | Session replay enabled by default — opt-out required, not opt-in

  • What: sessionReplayEnabled returns true when key absent (first launch). New user's session screen-recorded from first app open until explicitly disabled in Settings.
  • Impact: May violate GDPR Article 7 and CCPA, especially given sensitive nature (mental health data) of what is displayed on screen.

Shared/Services/PhotoManager.swift:104-134 | WARNING | Synchronous disk I/O for photo loading on main actor

  • What: loadPhoto(id:) and loadThumbnail(id:) call Data(contentsOf: fullURL) synchronously on the main actor. Photos can be megabytes in size.
  • Impact: UI freezes for duration of each photo disk read. Visible jank when day view scrolls past photo entries.

Accessibility Auditor (46 findings)

Severity File Finding
CRITICAL Shared/Views/LockScreenView.swift:1615 No accessibility labels on any element — VoiceOver users cannot unlock the app
CRITICAL Shared/Views/AddMoodHeaderView.swift:515 NeonVotingView has no accessibilityElement container — Canvas decoratives fully traversable
BUG Shared/Views/YearView/YearView.swift:724 YearHeatmapCell label is only "Mood entry" — no date, mood, or button trait
BUG Shared/Views/YearView/YearView.swift:718 YearHeatmapCell tappable but has no .isButton trait or hint
BUG Shared/Views/CustomizeView/SubViews/PersonalityPackPickerView.swift:52 .alert inside ForEach — only last row's alert fires
BUG Shared/Views/SettingsView/SettingsTabView.swift:61 CustomizeContentView missing iapManager/authManager environment objects — guaranteed crash
BUG Shared/Views/SettingsView/LiveActivityPreviewView.swift:325 ImageRenderer used on background thread — produces corrupt images
BUG Shared/Views/CelebrationAnimations.swift:109 onComplete() fires after view dismissal — potential duplicate mood entry
BUG Shared/Views/LockScreenView.swift:1694 Second biometric failure shows no error — user stuck on lock screen
BUG Shared/Views/Views/IAPWarningView.swift:56 FeelsSubscriptionStoreView sheet missing iapManager @EnvironmentObject
BUG Shared/Views/SharingStylePickerView.swift:206 designs[selectedIndex] without bounds check — crashes when designs is empty
BUG Shared/Views/PhotoPickerView.swift:151 isProcessing = true mutated from non-@MainActor async context
BUG Shared/Views/SharingTemplates/WeekTotalTemplate.swift:30 Raw developer string "WeekTotalTemplate body" shown to VoiceOver users
WARNING Shared/Views/MonthView/MonthView.swift:39 Weekday headers are single ambiguous chars ("T", "S") — duplicate IDs for VoiceOver
WARNING Shared/Views/MonthView/MonthView.swift:679 .accessibilityHint("Double tap to edit") is hardcoded English, not localized
WARNING Shared/Views/EntryListView.swift:97 Accessibility hints use inline String(localized:) — may not translate
WARNING Shared/Views/EntryListView.swift:560 Chronicle style hardcoded English strings not localized
WARNING Shared/Views/EntryListView.swift:722 Neon style renders "NO_DATA" raw developer string
WARNING Shared/Views/EntryListView.swift:2242 Accelerometer starts on .onAppear, never stopped
WARNING Shared/Views/DayView/DayView.swift:401 "SIDE A" hardcoded English decoration announced by VoiceOver
WARNING Shared/Views/DayView/DayView.swift:556 "avg" abbreviation not localized, no accessibility label override
WARNING Shared/Views/FeelsSubscriptionStoreView.swift:129 All paywall marketing copy hardcoded English — not localized
WARNING Shared/Views/FeelsSubscriptionStoreView.swift:51 dismiss() in async Task after status check — may dismiss wrong view
WARNING Shared/Onboarding/views/OnboardingTime.swift:65 DatePicker hardcoded .colorScheme(.light) — dark mode users see jarring white picker
WARNING Shared/Onboarding/views/OnboardingTime.swift:87 Reminder time sentence is hardcoded English, not localizable
WARNING Shared/Onboarding/views/OnboardingCustomizeOne.swift:39 .foregroundColor(.black) hardcoded — fails contrast in dark mode
WARNING Shared/Onboarding/views/OnboardingCustomizeTwo.swift:37 .foregroundColor(.white) hardcoded — fails contrast in light mode
WARNING Shared/Onboarding/views/OnboardingStyle.swift:146 UIColor.darkText/darkGray don't adapt to dark mode — WCAG contrast failure
WARNING Shared/Models/Theme.swift:191 AlwaysLight.bgColor returns dark background in dark system mode
WARNING Shared/Views/NoteEditorView.swift:59 Navigation title "Journal Note" hardcoded English
WARNING Shared/Views/NoteEditorView.swift:91 Mood icon in entry header has no accessibility label
WARNING Shared/Views/InsightsView/InsightsView.swift:97 Pull-to-refresh calls refreshInsights() without await — spinner dismisses before completion
WARNING Shared/Views/CustomizeView/SubViews/VotingLayoutPickerView.swift:25 "Voting Layout" section title hardcoded English
WARNING Shared/Views/CustomizeView/CustomizeView.swift:168 Customize rows use .onTapGesture without .isButton trait or hint
WARNING Shared/Views/CustomizeView/SubViews/ImagePackPickerView.swift:5 Icon pack rows have label only — no hint, no .isButton trait
WARNING Shared/Views/CustomizeView/SubViews/ShapePickerView.swift:43 Shape picker preview uses randomElement()! — accessibility label changes unpredictably
WARNING Shared/Views/CustomizeView/SubViews/DayFilterPickerView.swift:12 Singleton DaysFilterClass.shared in @StateObject — SwiftUI lifecycle bypassed
WARNING Shared/Views/SettingsView/SettingsView.swift:1733 Debug button misspelling "luanch" announced verbatim by VoiceOver
WARNING Shared/Views/SettingsView/SettingsView.swift:1817 Force-unwrap URL literals for Privacy Policy and EULA buttons
WARNING Shared/Views/Views/ExportView.swift:100 "Export Data" and "Cancel" hardcoded English
WARNING Shared/Views/Views/ExportView.swift:319 Error message "Failed to create export file" hardcoded English
WARNING Shared/Views/MonthView/MonthDetailView.swift:40 Share image rendered synchronously on main thread — blocks UI during VoiceOver share
WARNING Shared/Views/PurchaseButtonView.swift:127 "Payment Issue" badge text hardcoded English
WARNING Shared/Views/CustomizeView/SubViews/IconPickerView.swift:73 setAlternateIconName error silently swallowed — no user feedback
WARNING Shared/Views/CustomizeView/SubViews/IconPickerView.swift:89 .fill().foregroundColor() incorrect modifier order — wrong selection color
WARNING Shared/Utilities/AccessibilityHelpers.swift:74 .accessibilityHint("") applied with empty string — VoiceOver may announce blank hint
WARNING Shared/Utilities/AccessibilityHelpers.swift:33 value: UUID() on animation triggers on every render
WARNING Shared/Utilities/AccessibilityHelpers.swift:13 Custom \.reduceMotion env key defined but never read
WARNING Shared/Views/SharingTemplates/Variations/CurrentStreakVariations.swift:210 "Last 10 Days" hardcoded, inaccurate when fewer entries
WARNING Shared/Views/AddMoodHeaderView.swift:700 NeonBarButtonStyle ignores accessibilityReduceMotion
WARNING Shared/Models/MoodTintable.swift:78 All 5 custom colors default to same #a92b26 — indistinguishable for color-blind users

Energy/Battery Auditor (23 findings)

Severity File Finding
CRITICAL Shared/Views/EntryListView.swift:2224 CMMotionManager started at 30 Hz in .onAppear, stop() never called — continuous battery drain
CRITICAL Shared/DemoAnimationManager.swift:91 60 Hz Timer spawns new Task on every tick — up to 18,000 Task allocations over animation lifetime
CRITICAL Shared/BGTask.swift:14 Background task annotated @MainActor — Core Data fill runs on main thread, defeats background intent
CRITICAL Shared/Views/SettingsView/LiveActivityPreviewView.swift:325 ImageRenderer called on DispatchQueue.global — threading violation, corrupt images
BUG Shared/DemoAnimationManager.swift:54 DispatchQueue.main.asyncAfter callbacks not cancellable — multiple timers accumulate on rapid restarts
BUG Shared/MoodStreakActivity.swift:215 Infinite loop if Calendar.date(byAdding:) returns nil — main actor hang
BUG Shared/FeelsApp.swift:92 Task.detached { @MainActor in } defeats detached — all Core Data work on main thread
BUG Shared/Views/LockScreenView.swift:1711 try? Task.sleep swallows CancellationError — auth prompt after view disappears
BUG Shared/Views/CelebrationAnimations.swift:109 DispatchQueue.asyncAfter fires mood-save after view dismissal — potential duplicate entries
BUG Shared/Views/SettingsView/LiveActivityPreviewView.swift:89 animationTimer not nil'd after invalidation — dangling timer reference
BUG Shared/Services/PhotoManager.swift:104 Synchronous disk I/O on @MainActor — blocks scroll frame budget on photo loads
BUG Shared/Views/FeelsSubscriptionStoreView.swift:51 dismiss() called after async op in stale Task — paywall may not dismiss after purchase
PERFORMANCE Shared/MoodStreakActivity.swift:306 Two long-duration Timers scheduled without .common run-loop mode — freeze during scroll
PERFORMANCE Shared/Views/EntryListView.swift:2249 Motion callbacks on .main queue with withAnimation at 30 Hz — 30 animation transactions/sec
PERFORMANCE Shared/Views/InsightsView/InsightsViewModel.swift:88 withTaskGroup children all @MainActor — zero actual concurrency, 3× slower
PERFORMANCE Shared/Views/InsightsView/InsightsViewModel.swift:60 generateInsights() spawns new Task on every call — concurrent stale LLM requests
PERFORMANCE Shared/Views/FeelsSubscriptionStoreView.swift:164 repeatForever animations not cancelled on dismiss — GPU work during teardown
PERFORMANCE Shared/Views/AddMoodHeaderView.swift:343 OrbitVotingView/NeonVotingView repeatForever pulse — conflicting states on rapid layout switches
PERFORMANCE FeelsWidget2/WidgetProviders.swift:127 Widget reload policy now+10s, first entry dated now+15s — unnecessary 5-second early reload
PERFORMANCE FeelsWidget2/FeelsTimelineWidget.swift:73 DateFormatter computed property — new allocation on every widget render (24 per render)
PERFORMANCE FeelsWidget2/FeelsVoteWidget.swift:68 DateFormatter and multi-UserDefaults JSON decodes per widget render
PERFORMANCE Shared/Utilities/AccessibilityHelpers.swift:33 value: UUID() on .animation triggers animation on every render pass
PERFORMANCE Shared/Views/YearView/YearViewModel.swift:44 filterEntries double-clear causes two empty-state flashes + synchronous Core Data on main thread

Storage Auditor (28 findings)

Severity File Finding
CRITICAL Shared/Services/PhotoManager.swift:26 Photos stored in App Group container root without .isExcludedFromBackupKey — double-backed up to iCloud
CRITICAL Shared/Persisence/SharedModelContainer.swift:86 SwiftData store at App Group container root — wrong location, not guaranteed preserved on migration
CRITICAL Shared/Persisence/ExtensionDataProvider.swift:91 Widget opens same SQLite store as main app with cloudKitDatabase: .none — incompatible CloudKit configs
CRITICAL Shared/SharedMoodIntent.swift:93 New ModelContainer per widget intent invocation — concurrent writers, WAL contention
CRITICAL Shared/Random.swift:28 Force-unwrap UserDefaults(suiteName:)! — crash if App Group entitlement unavailable
CRITICAL Shared/Models/UserDefaultsStore.swift:226 App Group UserDefaults unprotected — hasActiveSubscription, privacyLockEnabled readable while locked
CRITICAL Shared/Persisence/DataControllerADD.swift:13 Non-atomic delete-save-insert — data loss if second save fails
CRITICAL Shared/Persisence/ExtensionDataProvider.swift:204 Same non-atomic delete-save-insert in widget extension — higher failure risk in memory-constrained process
CRITICAL Shared/Analytics.swift:24 PostHog API key hardcoded in source — permanently in git history
CRITICAL Shared/Persisence/DataController.swift:15 DataController.shared static let on @MainActor class — init can run off-main-actor
BUG Shared/Services/PhotoManager.swift:34 Photos directory at App Group container root — may be lost during OS migration
BUG Shared/Services/PhotoManager.swift:95 Thumbnail write errors silently swallowed; thumbnails not excluded from backup
BUG Shared/Persisence/SharedModelContainer.swift:66 Silent fallback to in-memory storage when App Group unavailable — all data ephemeral
BUG Shared/IAPManager.swift:68 firstLaunchDate falls back to Date() on every cold launch — trial clock resets
BUG Shared/IAPManager.swift:206 Offline subscription fallback reads unprotected App Group plist — editable by attacker
BUG Shared/Services/BiometricAuthManager.swift:17 isUnlocked defaults to true — content briefly visible before authentication completes
BUG Shared/Views/SettingsView/SettingsView.swift:1158 URL(string: "shareddocuments://\(url.path)")! — crashes on paths with special characters
BUG Shared/Services/ImageCache.swift:24 NotificationCenter.addObserver token leaked — observer never removed
WARNING Shared/Services/ExportService.swift:80 Export files written to temporaryDirectory without cleanup — orphaned files accumulate
WARNING Shared/Services/ExportService.swift:171 temporaryDirectory file may be purged before share sheet completes
WARNING Shared/Models/UserDefaultsStore.swift:226 All prefs including sensitive values stored in shared App Group defaults — too broad surface
WARNING Shared/Models/UserDefaultsStore.swift:217 Static cachedOnboardingData with no thread synchronization — data race
WARNING Shared/Models/UserDefaultsStore.swift:309 Deprecated UserDefaults.synchronize() called — no-op since iOS 12
WARNING Shared/Models/OnboardingDataDataManager.swift:27 Second synchronize() call site
WARNING Shared/Services/ReviewRequestManager.swift:74 Review state in UserDefaults.standard — moods logged via widget never increment counter
WARNING Shared/Analytics.swift:28 Analytics opt-out state in UserDefaults — no file protection level enforcement
WARNING Shared/Services/PhotoManager.swift:187 UIGraphicsBeginImageContextWithOptions deprecated in iOS 17
WARNING FeelsWidget2/FeelsTimelineWidget.swift:236 Widget fetches from Unix epoch — loads entire history into 30 MB widget extension memory limit

iCloud/CloudKit Auditor (22 findings)

Severity File Finding
CRITICAL Shared/Persisence/SharedModelContainer.swift:34 Main app and widget write to same SQLite file with different CloudKit configs — unsupported, can corrupt store
CRITICAL Shared/SharedMoodIntent.swift:93 New ModelContainer per widget intent — concurrent SQLite writers, WAL uncommitted frames
CRITICAL Shared/SharedMoodIntent.swift:109 Non-atomic delete-save-insert — CloudKit can sync delete before insert arrives on other device
CRITICAL Shared/Persisence/DataControllerADD.swift:19 Delete-save-insert non-atomic — CloudKit push between two saves causes remote device to lose entry
CRITICAL Shared/Persisence/ExtensionDataProvider.swift:198 Watch extension same non-atomic pattern with CloudKit enabled — data loss during poor connectivity
CRITICAL Shared/Persisence/DataControllerGET.swift:74 Infinite loop in calculateStreak — main actor deadlock from nil Calendar result
BUG Shared/Persisence/DataController.swift:78 refreshFromDisk() calls modelContext.rollback() — discards pending CloudKit-delivered changes
BUG Shared/FeelsApp.swift:92 Task.detached { @MainActor in } runs Core Data work on main thread — blocks during CloudKit merges
BUG Shared/Persisence/ExtensionDataProvider.swift:61 Widget opens CloudKit-managed store as local-only — corrupts _cloudkit_metadata table
BUG Shared/Persisence/DataController.swift:15 Static DataController.shared on @MainActor — init can run on wrong thread
BUG Shared/Persisence/DataController.swift:55 saveAndRunDataListeners() runs listeners even when save() fails — UI shows unsaved changes
BUG Shared/Persisence/DataController.swift:51 addNewDataListener grows unboundedly — stale closures execute on every CloudKit refresh
BUG Shared/BGTask.swift:22 @MainActor BGTask marks complete before async processPendingSideEffects() finishes
BUG Shared/BGTask.swift:36 Force-unwrap on Calendar date in BGTask — crashes background scheduler
BUG Shared/Persisence/SharedModelContainer.swift:66 Silent fallback to in-memory on App Group unavailability — CloudKit sync never runs
WARNING FeelsWidgetExtension.entitlements:1 Widget declares CloudKit entitlement it must never use — potential App Store rejection
WARNING FeelsWidgetExtension.entitlements:1 Wrong entitlement file selection silently falls back to in-memory storage
WARNING Shared/Persisence/ExtensionDataProvider.swift:84 Debug/release group mismatch can cause nil container URL — in-memory fallback
WARNING Shared/Models/MoodEntryModel.swift:22 No versioned SwiftData schema migration — enum reordering silently corrupts all CloudKit records
WARNING Shared/Persisence/DataControllerADD.swift:46 +12 hours timezone hack in fillInMissingDates — dates may land on wrong day after CloudKit sync
WARNING Shared/Persisence/DataControllerGET.swift:18 entry.forDate <= endDate inclusive bound — entries at exact midnight of next day double-counted
WARNING Shared/Models/UserDefaultsStore.swift:217 cachedOnboardingData data race — wrong dates used for gap-filling, synced to CloudKit

SwiftUI Architecture Auditor (49 findings — selected)

Severity File Finding
CRITICAL Shared/Views/SettingsView/SettingsTabView.swift:62 CustomizeContentView missing iapManager/authManager environment objects — crash on Customize tab
CRITICAL Shared/Views/MonthView/MonthView.swift:29 Singleton OnboardingDataDataManager.shared wrapped in @StateObject — SwiftUI lifecycle violated
CRITICAL Shared/Views/MonthView/MonthView.swift:49 Singleton DemoAnimationManager.shared in @StateObject — conflicting owner semantics
CRITICAL Shared/Views/CustomizeView/SubViews/DayFilterPickerView.swift:12 Singleton DaysFilterClass.shared in @StateObject — changes may not propagate
CRITICAL Shared/Persisence/DataControllerGET.swift:74 Infinite loop in calculateStreak?? checkDate nil-coalescing never advances
CRITICAL Shared/MoodStreakActivity.swift:215 Identical infinite loop in LiveActivityScheduler.calculateStreak
CRITICAL Shared/AppShortcuts.swift:136 while true streak loop on @MainActor — thousands of Core Data fetches for long-streak users
CRITICAL Shared/Views/MoodEntryFunctions.swift:25 newGrouped[year] = newMonth inside inner loop — all months but last dropped per year
BUG Shared/Views/DayView/DayViewViewModel.swift:29 Double force-unwrap year![$0]!.count in numberOfEntries
BUG Shared/Views/DayView/DayViewViewModel.swift:115 Force-unwrap on DateComponents.month!/.year!
BUG Shared/Views/YearView/YearViewModel.swift:27 updateData() only sets dates, never populates data or entriesByYear — misleading name
BUG Shared/Views/YearView/YearViewModel.swift:48 Double removeAll() causes two empty-state re-renders with empty dict
BUG Shared/Views/YearView/YearView.swift:334 YearCard.Equatable compares only entries.count — same count with different moods prevents re-render
BUG Shared/Views/MonthView/MonthView.swift:413 MonthCard.Equatable excludes demoManager — demo animations silently stop
BUG Shared/Views/MonthView/MonthView.swift:176 ForEach id: \.element.month collides across years — SwiftUI drops or misidentifies month cards
BUG Shared/Views/InsightsView/InsightsViewModel.swift:59 Unstructured task on generateInsights() — race condition with concurrent task mutations
BUG Shared/Views/InsightsView/InsightsViewModel.swift:77 Force-unwrap on Calendar dateComponents — crash on non-Gregorian calendars
BUG Shared/Views/Views/PersonalityPackPickerView.swift:52 .alert inside ForEach — only last row's alert fires
BUG Shared/Views/CustomizeView/SubViews/DayFilterPickerView.swift:21 addFilter/removeFilter are no-ops — buttons do nothing silently
BUG Shared/Views/CelebrationAnimations.swift:109 DispatchQueue.asyncAfter mood-save fires after dismissal — ghost/duplicate entries
BUG Shared/BGTask.swift:42 Error interpolation typo (error) — scheduling failures invisible
BUG Shared/BGTask.swift:14 @MainActor BGTask runs Core Data fill on main thread
BUG Shared/Models/MoodEntryFunctions.swift:25 Inner loop overwrites newGrouped[year] — confirmed data display bug for multi-year users
BUG Shared/Views/AddMoodHeaderView.swift:20 @State var onboardingData captures value once — stale header after Settings changes
BUG Shared/Onboarding/views/OnboardingMain.swift:12 Reference-type OnboardingData wrapped in @State — changes not observed
BUG Shared/Onboarding/views/OnboardingSubscription.swift:139 Onboarding completion fires on any sheet dismiss — including cancel
BUG Shared/Models/BGView.swift:76 BGView.Equatable == hardcoded to true — view never re-renders on prop changes
BUG Shared/Models/BGView.swift:52 randomMood computed — new random value on every render, background flickers
BUG Shared/Random.swift:65 monthSymbols[fromMonthInt-1] — out-of-bounds crash when fromMonthInt == 0
BUG Shared/Views/SharingTemplates/LongestStreakTemplate.swift:46 Non-@MainActor init calls @MainActor configureData() — Swift 6 violation, no-op writes
BUG Shared/Services/LiveActivityPreviewView.swift:325 ImageRenderer on background thread — corrupt images
WARNING Shared/Views/DayView/DayView.swift:17 Dead @AppStorage deleteEnabled — every UserDefaults write triggers needless body re-evaluation
WARNING Shared/Views/DayView/DayView.swift:32 Dead @State showTodayInput
WARNING Shared/Views/YearView/YearView.swift:13 Dead @State toggle = true
WARNING Shared/Views/MonthView/MonthDetailView.swift:21 Duplicate showingUpdateEntryAlert/showUpdateEntryAlert booleans — dead state
WARNING Shared/Views/EntryListView.swift:2073 @ObservedObject on singleton in struct — hundreds of observation points
WARNING Shared/Views/InsightsView/InsightsViewModel.swift:88 withTaskGroup children all @MainActor — serialized, no concurrency benefit
WARNING Shared/Views/CustomizeView/CustomizeView.swift:295 Hidden Text hack to force re-renders — fragile, may be optimized away by Apple
WARNING Shared/Views/MonthView/MonthView.swift:342 Two .onAppear modifiers on same view
WARNING Shared/Views/SettingsView/SettingsView.swift:94 OnboardingMain sheet missing iapManager environment object injection
WARNING Shared/IAPManager.swift:289 try? on subscription status silently grants premium on StoreKit errors
WARNING Shared/IAPManager.swift:40 #if DEBUG bypass block is dead code — both branches are false
WARNING Shared/Services/WatchConnectivityManager.swift:117 pendingMoods data race — WCSession delegate vs main thread
WARNING Shared/Services/PhotoManager.swift:104 Synchronous disk I/O on @MainActor — main thread blocked during photo loads
WARNING Shared/Services/ImageCache.swift:24 NotificationCenter observer token leaked
WARNING Shared/Views/SettingsView/SettingsTabView.swift:157 NavigationView deprecated — iPad shows unwanted split layout
WARNING Shared/Utilities/AccessibilityHelpers.swift:33 value: UUID() triggers animation on every render
WARNING Shared/Onboarding/views/OnboardingTime.swift:13 DateFormatter computed property re-allocates on every DatePicker tick
WARNING Shared/Views/NoteEditorView.swift:474 Force-unwrap entry.photoID! despite if let guard present

Swift Performance Auditor (31 findings)

Severity File Finding
CRITICAL Shared/AppShortcuts.swift:128 while true on @MainActor — one Core Data fetch per day, up to 1000+ for long streaks
CRITICAL Shared/Persisence/DataControllerGET.swift:74 Infinite loop if calendar.date(byAdding:) returns nil in streak calculation
CRITICAL Shared/MoodStreakActivity.swift:215 Same infinite loop in LiveActivityScheduler.calculateStreak
CRITICAL FeelsWidget2/FeelsTimelineWidget.swift:236 Widget fetches from Unix epoch — entire history in memory-constrained extension process
PERFORMANCE Shared/Views/YearView/YearViewModel.swift:36 O(n log n) sort to find minimum date — use min(by:) instead
PERFORMANCE Shared/Views/YearView/YearViewModel.swift:48 Double removeAll() on @Published dicts — two re-renders with empty state
PERFORMANCE Shared/Views/YearView/YearViewModel.swift:44 Synchronous Core Data + O(n) grid build on @MainActor on every filter change
PERFORMANCE Shared/Random.swift:91 createTotalPerc runs 5 separate O(n) filters — use single-pass histogram
PERFORMANCE Shared/Views/InsightsView/InsightsViewModels/MoodDataSummarizer.swift:213 Two full O(n log n) sorts in one function call on already-sorted input
PERFORMANCE Shared/Views/InsightsView/InsightsViewModels/MoodDataSummarizer.swift:265 Redundant sort inside calculateMoodStreak called on already-sorted input
PERFORMANCE Shared/Models/MoodTintable.swift:109 getCustomMoodTint() called 10 times per render — each call decodes JSON from UserDefaults
PERFORMANCE Shared/Views/ChartDataBuildable.swift:31 String interpolation for dict key inside inner loop — 1000 String allocs for 1000 entries
PERFORMANCE Shared/MoodLogger.swift:134 ISO8601DateFormatter() allocated fresh on every markSideEffectsApplied/sideEffectsApplied
PERFORMANCE FeelsWidget2/FeelsTimelineWidget.swift:73 DateFormatter as computed property — new allocation on every widget render (×3 sizes)
PERFORMANCE FeelsWidget2/FeelsVoteWidget.swift:68 DateFormatter inside computed votingDateString + multiple UserDefaults decodes per render
PERFORMANCE Shared/Views/DemoAnimationManager.swift:91 60 Hz Timer spawning new Task per tick — tasks queue up under load
PERFORMANCE Shared/Utilities/AccessibilityHelpers.swift:33 value: UUID() triggers animation on every render pass
PERFORMANCE Shared/Onboarding/views/OnboardingTime.swift:13 DateFormatter computed on DatePicker view — new allocation every ~16ms during scroll
PERFORMANCE Shared/Views/Views/ExportView.swift:177 DateFormatter re-created on every dateRangeText access
PERFORMANCE Shared/Views/CustomizeView/SubViews/ImagePackPickerView.swift:50 O(n log n) sort inside ForEach per row to find max rawValue — use max(by:)
PERFORMANCE Shared/Views/CustomizeView/SubViews/PersonalityPackPickerView.swift:64 Same sorted-allCases anti-pattern
PERFORMANCE Shared/Views/InsightsView/InsightsViewModels/MoodDataSummarizer.swift:158 weekdayNames[weekday - 1] — no bounds check, crashes on weekDay == 0
PERFORMANCE Shared/Random.swift:28 groupDefaults computed static var — UserDefaults(suiteName:) called on every access
PERFORMANCE Shared/Views/DateFormattingCache.swift:263 DateFormattingCache.shared accessed from multiple threads with no synchronization
PERFORMANCE Shared/Views/BGView.swift:52 randomMood computed — new random value every render, all 40100 background cells re-randomize
PERFORMANCE Shared/Views/SettingsView/SettingsView.swift:1158 Force-unwrap URL from runtime path — crash if path has special characters
PERFORMANCE Shared/Random.swift:48 existingWeekdayName/existingDayFormat mutable static dicts — no thread synchronization
PERFORMANCE Shared/Random.swift:174 textToImageCache static dict — no thread synchronization, concurrent widget + app rendering
PERFORMANCE Shared/Services/HealthService.swift:440 365 concurrent HealthKit task group tasks — floods cooperative thread pool
PERFORMANCE Shared/Persisence/DataControllerGET.swift:89 splitIntoYearMonth fetches from epoch — loads entire history into memory
PERFORMANCE Shared/Views/InsightsView/InsightsViewModel.swift:88 withTaskGroup child tasks all @MainActor — LLM calls run serially not concurrently

Modernization Auditor (43 findings — selected)

Severity File Finding
CRITICAL Shared/Analytics.swift:24 PostHog API key hardcoded in source — permanently in git history
CRITICAL Shared/Views/SettingsView/SettingsTabView.swift:62 CustomizeContentView missing environment objects — crash
CRITICAL Shared/Views/CelebrationAnimations.swift:109 DispatchQueue.asyncAfter mood-save fires after dismissal — duplicate entries
CRITICAL Shared/MoodStreakActivity.swift:215 Infinite loop in streak calculation
CRITICAL Shared/Persisence/DataControllerGET.swift:74 Infinite loop in streak calculation
CRITICAL Shared/AppShortcuts.swift:136 while true streak loop on main actor
CRITICAL Shared/Views/MoodEntryFunctions.swift:25 Inner loop overwrites year grouping — all months but last lost
CRITICAL Shared/Views/MonthView/MonthView.swift:176 ForEach id: \.month ID collision across years
BUG Shared/IAPManager.swift:289 try? grants premium on StoreKit errors
BUG Shared/BGTask.swift:42 (error) typo — scheduling failures invisible
BUG Shared/BGTask.swift:14 @MainActor BGTask runs Core Data on main thread
BUG Shared/FeelsApp.swift:92 Task.detached { @MainActor in } contradictory pattern
BUG Shared/Services/HealthService.swift:79 isAuthorized = true unconditional — HealthKit denial treated as success
BUG Shared/Views/PhotoPickerView.swift:151 @State mutation from non-@MainActor async context
BUG Shared/Views/SettingsView/SettingsView.swift:1158 Force-unwrap URL with runtime path
BUG Shared/Views/SharingTemplates/LongestStreakTemplate.swift:52 Non-@MainActor init calling @MainActor function
BUG Shared/Onboarding/views/OnboardingSubscription.swift:139 Onboarding completion unconditional on dismiss
BUG Shared/Services/WatchConnectivityManager.swift:117 pendingMoods data race
WARNING Shared/Services/PhotoManager.swift:187 UIGraphicsBeginImageContextWithOptions deprecated iOS 17
WARNING Shared/Random.swift:187 Second UIGraphicsBeginImageContext deprecated call site
WARNING Shared/Models/UserDefaultsStore.swift:309 synchronize() deprecated no-op — 3 call sites
WARNING Shared/FeelsTips.swift:257 DispatchQueue.main.asyncAfter inside .onAppear — should use .task { try? await Task.sleep }
WARNING Shared/Views/NoteEditorView.swift:84 DispatchQueue.asyncAfter for @FocusState — fixed in SwiftUI 5.0+
WARNING Shared/Views/SettingsView/LiveActivityPreviewView.swift:325 ImageRenderer on background thread
WARNING Shared/Views/InsightsView/InsightsView.swift:97 refreshInsights() called without await — spinner dismisses before completion
WARNING Shared/Views/InsightsView/InsightsViewModel.swift:88 withTaskGroup child tasks serialized by @MainActor
WARNING Shared/Views/EntryListView.swift:2242 CMMotionManager never stopped
WARNING Multiple files 8 files use deprecated @Environment(\.presentationMode) — replace with @Environment(\.dismiss)
WARNING Multiple files (10) .edgesIgnoringSafeArea(.all) deprecated — replace with .ignoresSafeArea()
WARNING Shared/Models/Theme.swift:74 Themeable protocol mandates AnyView return types — prevents compiler optimization
WARNING Shared/Models/Shapes.swift:17 AnyView in BGShape.view() — defeats SwiftUI structural diffing
WARNING Shared/Views/SwitchableView.swift:301 AnyView for all switchable view cases
WARNING Shared/Services/HealthService.swift:154 withCheckedContinuation wrapping HKQuery — errors silently dropped as values
WARNING Shared/Analytics.swift:615 @MainActor method called from non-isolated ViewModifier closure — Swift 6 error
WARNING Shared/Views/EntryListView.swift:119 .foregroundColor() soft-deprecated iOS 17 — 719 occurrences across 66 files
WARNING 47 files PreviewProvider pattern — replace with #Preview macro for faster previews
WARNING Shared/Views/DemoAnimationManager.swift:91 Timer + Task wrapping on @MainActor — call updateProgress() directly
WARNING Shared/Views/YearView/YearViewModel.swift:11 ObservableObject candidate for @Observable migration
WARNING Shared/Views/DayView/DayViewViewModel.swift:13 ObservableObject candidate for @Observable migration
WARNING Shared/Onboarding/OnboardingData.swift:13 @Published mutations need @MainActor protection
WARNING Shared/Views/MonthView/MonthView.swift:31 Nested ObservableObject class without @MainActor
WARNING Shared/Onboarding/views/OnboardingMain.swift:12 Reference-type class wrapped in @State — should be @StateObject
WARNING Shared/Models/MoodTintable.swift:78 SavedMoodTint has unnecessary NSObject inheritance — prevents @Observable migration

Navigation Auditor (27 findings)

Severity File Finding
CRITICAL Shared/Views/SettingsView/SettingsTabView.swift:62 CustomizeContentView missing iapManager environment object — crash on Customize tab
CRITICAL Shared/Views/CustomizeView/SubViews/AppThemePickerView.swift:262 theme.previewColors[0]/[1] unchecked subscript — crash if fewer than 2 colors
CRITICAL Shared/Views/Sharing/SharingStylePickerView.swift:206 designs[selectedIndex] no bounds check in LongestStreakPickerView — crash when designs empty
BUG Shared/FeelsApp.swift:30 Force cast task as! BGProcessingTask — crash if system delivers different task type
BUG Shared/Views/MainTabView.swift:53 Onboarding sheet empty onDismissneedsOnboarding never set false if system dismisses
BUG Shared/Views/DayView/DayView.swift:48 .sheet(item: $selectedEntry) — stale entry reference on concurrent data update
BUG Shared/Views/MonthView/MonthView.swift:176 ForEach id: \.element.month — duplicate IDs across years, months silently dropped
BUG Shared/Views/MonthView/MonthView.swift:339 showSubscriptionStore sheet missing iapManager environment object — crash on month gate
BUG Shared/Views/FeelsSubscriptionStoreView.swift:47 dismiss() in async Task after status check — stale environment, potential double-dismiss
BUG Shared/Views/SettingsView/SettingsView.swift:94 OnboardingMain sheet missing iapManager — crash on "Show Onboarding" in Settings
BUG Shared/Views/SettingsView/SettingsView.swift:1158 Force-unwrap URL from runtime path — crash on special characters
BUG Shared/Views/CelebrationAnimations.swift:109 Un-cancellable asyncAfter mood-save fires after navigation away — duplicate entries
BUG Shared/Views/CustomizeView/SubViews/AppThemePickerView.swift:117 Double-dismiss: selectedTheme = nil dismisses sheet, then explicit dismiss() also called
BUG Shared/Onboarding/views/OnboardingSubscription.swift:139 Onboarding completion unconditional on sheet dismiss
BUG Shared/Views/CustomizeView/CustomizeView.swift:529 as? CustomWidgetModel cast on NSCopying.copy() — nil result presents blank sheet
BUG Shared/Views/DayView/DayViewViewModel.swift:29 Double force-unwrap in numberOfEntries
BUG Shared/Views/Views/EntryListView.swift (multiple) Multiple .sheet modifiers on same view — only one reliably presents on iOS 16.4 and earlier
WARNING Shared/Views/DayView/DayView.swift:46 showingSheet boolean bound to SettingsView — never triggered, permanently unreachable dead sheet
WARNING Shared/Views/MainTabView.swift:18 onboardingData captured as non-reactive let — stale after CloudKit sync
WARNING Shared/Views/MainTabView.swift:79 UIApplication.shared.connectedScenes.first — wrong scene on iPad multi-window
WARNING Shared/Onboarding/views/OnboardingMain.swift:18 TabView pager with no navigation guards — user can swipe to completion without filling fields
WARNING Shared/Views/MonthView/MonthView.swift:350 Sheet item driven by optional selectedDetail.selectedItem — nil content if race between item and show
WARNING Shared/Views/MonthView/MonthDetailView.swift:85 Multiple rapid taps on share enqueue multiple showSheet = true — stale image in sheet
WARNING Shared/Views/InsightsView/InsightsView.swift:97 refreshInsights() not awaited — spinner dismisses before load completes
WARNING Shared/Views/InsightsView/InsightsViewModel.swift:60 generateInsights() no deduplication — concurrent tasks interleave published state
WARNING Shared/Onboarding/views/OnboardingStyle.swift:70 theme.apply() called immediately on every tap — no rollback if onboarding abandoned
WARNING Shared/Views/NoteEditorView.swift:32 Inner NavigationStack inside modal sheet — orphaned from app navigation hierarchy

Testing Auditor (47 findings — selected)

Severity File Finding
CRITICAL Tests iOS/Tests_iOS.swift:1 Only 2 active unit test methods — cover one utility function; entire business logic layer untested
CRITICAL Tests iOS/Tests_iOS.swift:1 Zero Swift Testing (@Test/#expect) migration — XCTest boilerplate prevents async actor testing
CRITICAL Shared/Persisence/DataController.swift:15 DataController.shared singleton direct-access in 10+ views — untestable by design
CRITICAL Shared/Persisence/DataControllerProtocol.swift:1 DataControlling protocol exists but never used as DI type — dead testability infrastructure
CRITICAL Shared/MoodLogger.swift:16 MoodLogger wires 8 singleton side effects directly — impossible to unit test logging path
CRITICAL Shared/Persisence/DataControllerGET.swift:74 Infinite loop in calculateStreak — untestable without mock/injectable calendar
CRITICAL Shared/Persisence/DataControllerADD.swift:13 Non-atomic delete-save-insert — data loss untestable without mock context
CRITICAL Shared/AppShortcuts.swift:128 while true streak loop — O(n) Core Data per iteration, untestable without mock
CRITICAL Shared/MoodEntryFunctions.swift:25 Inner loop overwrites year grouping — zero test coverage
CRITICAL Shared/ShowBasedOnVoteLogics.swift:94 fatalError in production code — new DayOptions case crashes on launch
CRITICAL Shared/IAPManager.swift:289 try? grants premium on StoreKit error — zero IAPManager tests
CRITICAL Shared/Views/DayView/DayViewViewModel.swift:1 Primary view model directly accesses DataController.shared — completely untestable
BUG Shared/Date+Extensions.swift:16 Failable init?(rawValue:) never returns nil — bad input produces Jan 1, 2001 silently
BUG Shared/Date+Extensions.swift:93 dateRange(monthInt:yearInt:) hardcodes -8 hours — wrong for non-Pacific users
BUG Shared/Date+Extensions.swift:45 toLocalTime() double-applies timezone offset — wrong dates for non-UTC users
BUG Shared/IAPManager.swift:40 bypassSubscription is false in both #if DEBUG and #else branches — dead debug scaffold
BUG Shared/BGTask.swift:42 Error log typo (error) — scheduling failures always log literal text
BUG Shared/Views/YearView/YearViewModel.swift:27 updateData() doesn't populate data — transient blank year on cold launch
BUG Shared/Views/YearView/YearViewModel.swift:48 Double clear causes two empty-state flashes per refresh
BUG Shared/Views/MoodEntryFunctions.swift:43 Force-unwrap in padMoodEntriesMonth — crashes during DST spring-forward
BUG Shared/Onboarding/views/OnboardingSubscription.swift:139 Onboarding completion unconditional on dismiss
BUG Shared/Views/PersonalityPackPickerView.swift:52 .alert in ForEach — only last row fires
BUG Shared/Views/MonthView/MonthView.swift:176 ForEach id: \.month ID collision
BUG Shared/Views/MonthView/MonthView.swift:661 cachedMetrics only recalculated when empty — stale after mood edits
BUG Shared/Persisence/DataControllerDELETE.swift:32 deleteRandomFromLast in production code — accidental call irrecoverably deletes user data
BUG Shared/Views/SharingTemplates/LongestStreakTemplate.swift:46 Non-@MainActor init calls @MainActor function — Swift 6 violation
WARNING Shared/Analytics.swift:24 PostHog API key hardcoded — no test validates secure key loading
WARNING Shared/Models/UserDefaultsStore.swift:217 cachedOnboardingData race condition — no concurrency test
WARNING Shared/Views/EntryListView.swift:2242 Accelerometer never stopped — no lifecycle test
WARNING Shared/Views/CelebrationAnimations.swift:109 asyncAfter mood-save fires after dismissal — no test validates animation lifecycle
WARNING Shared/Services/ImageCache.swift:24 NotificationCenter observer leaked — no observer lifecycle test
WARNING Shared/Views/AccessibilityHelpers.swift:33 value: UUID() animation triggers every render — no test
WARNING Shared/Random.swift:65 monthSymbols[fromMonthInt-1] — no bounds check, called from 15+ views
WARNING Shared/Services/WatchConnectivityManager.swift:117 pendingMoods data race — no concurrency test
WARNING Shared/Views/SettingsView/LiveActivityPreviewView.swift:325 ImageRenderer on background thread — no threading test
WARNING Shared/Services/ExportService.swift:688 Force-unwrap in PDF generation critical path
WARNING Shared/IAPManager.swift:466 listenForTransactions task cancel in deinit — singleton never deinits, dead code
WARNING Shared/Views/InsightsView/InsightsViewModel.swift:60 Multiple concurrent generateInsights() — no concurrent state test
WARNING Shared/Views/InsightsView/InsightsViewModel.swift:88 withTaskGroup serialized — no performance baseline
WARNING Shared/Services/ReviewRequestManager.swift:117 Review request date recorded before dialog appears — no gating logic test
WARNING Shared/Services/HealthService.swift:79 isAuthorized = true unconditional — no auth state machine test
WARNING Shared/Models/MoodImagable.swift:87 All emoji image classes force-unwrap textToImage()! — crash under low memory
WARNING Shared/Onboarding/views/OnboardingWrapup.swift:1 OnboardingWrapup view not wired into OnboardingMain — dead code shipped in binary
WARNING Shared/Onboarding/views/OnboardingTitle.swift:38 All OnboardingTitle button actions commented out — view is a no-op
WARNING Tests macOS/Tests_macOS.swift:27 testExample() has no assertions — passes trivially
WARNING Tests macOS/Tests_macOS.swift:34 testLaunchPerformance has no baseline — no regression detection
WARNING Tests iOS/Tests_iOS.swift:1 XCTest only — zero Swift Testing adoption

Build Optimizer Auditor (38 findings)

Severity File Finding
PERFORMANCE Shared/Views/EntryListView.swift:1 2277-line file, 20-way switch in body — serializes compilation, no parallel build benefit
PERFORMANCE Shared/Views/FeelsSubscriptionStoreView.swift:1 2394-line file with 12+ independent marketing structs — entire paywall recompiles on any change
PERFORMANCE Shared/Views/SettingsView/SettingsView.swift:1 2118-line file with two near-duplicate views — duplicated type-checking cost
PERFORMANCE Shared/Views/LockScreenView.swift:1 2035-line file, 30+ types, AnyView protocol — type erasure overhead + serialized compile
PERFORMANCE Shared/Views/DayView/DayView.swift:1 934-line view with 16 @ViewBuilder section-header methods — large opaque result type graph
PERFORMANCE Shared/Views/MonthView/MonthView.swift:1 860-line view with double .onAppear and ForEach ID collision adding reconciliation work
PERFORMANCE Shared/Views/CelebrationAnimations.swift:1 850-line animation file — un-cancellable GCD callbacks, persistent repeatForever animations
PERFORMANCE Shared/Models/Theme.swift:74 Themeable protocol requires AnyView return types — forces type erasure across all 4 themes
PERFORMANCE Shared/Models/Shapes.swift:17 AnyView in BGShape.view() — defeats SwiftUI structural diffing for all entry cells
PERFORMANCE Shared/Views/SwitchableView.swift:301 AnyView for all switchable cases — full view replacement instead of identity diff
PERFORMANCE Shared/Views/Views/CustomizeView/CustomizeView.swift:295 Hidden Text re-render hack — fragile, may be optimized away by Apple
CRITICAL Shared/Persisence/DataControllerGET.swift:74 Infinite loop in calculateStreak
CRITICAL Shared/MoodStreakActivity.swift:215 Identical infinite loop in widget-path
CRITICAL Shared/AppShortcuts.swift:136 while true streak on @MainActor — thousands of fetches for long-streak users
CRITICAL FeelsWidget2/FeelsTimelineWidget.swift:236 Widget fetches from Unix epoch — entire history in 30MB widget process
BUG Shared/BGTask.swift:42 Error interpolation typo — scheduling failures invisible
BUG Shared/Views/CelebrationAnimations.swift:109 Un-cancellable asyncAfter mood-save
BUG Shared/SharedMoodIntent.swift:93 New ModelContainer per widget intent — SQLite contention
BUG Shared/FeelsApp.swift:92 Task.detached { @MainActor in } — Core Data on main thread at launch
BUG Shared/Views/EntryListView.swift:2242 CMMotionManager never stopped — continuous 60 Hz drain
BUG Shared/Models/BGView.swift:52 randomMood computed — all background cells re-randomize on every state change
PERFORMANCE FeelsWidget2/FeelsTimelineWidget.swift:73 DateFormatter computed property — new instance per widget render
PERFORMANCE FeelsWidget2/FeelsTimelineWidget.swift:87 createViews + moodTintable() called synchronously in widget view init
PERFORMANCE FeelsWidget2/FeelsVoteWidget.swift:68 DateFormatter + multiple UserDefaults JSON decodes per vote widget render
PERFORMANCE FeelsWidget2/WidgetProviders.swift:127 Reload policy now+10s vs entry date now+15s — unnecessary 5-second early reload
PERFORMANCE FeelsWidget2/WidgetModels.swift:1 WatchTimelineView is class — unexpected aliasing in WidgetKit timelines
PERFORMANCE Shared/Views/InsightsView/InsightsViewModel.swift:88 withTaskGroup child tasks all @MainActor — LLM calls serialized not parallel
PERFORMANCE Shared/Views/YearView/YearViewModel.swift:36 O(n log n) sort to find minimum date
PERFORMANCE Shared/Views/YearView/YearViewModel.swift:48 Double removeAll() — two empty-state re-renders
PERFORMANCE Shared/Models/MoodTintable.swift:112 getCustomMoodTint() called 10× per render — UserDefaults JSON decode on every access
PERFORMANCE Shared/Utilities/AccessibilityHelpers.swift:33 value: UUID() — animation triggers every render
PERFORMANCE Shared/Onboarding/views/OnboardingTime.swift:13 DateFormatter computed on DatePicker view — new instance every ~16ms during scroll
PERFORMANCE Shared/MoodLogger.swift:134 ISO8601DateFormatter() per call to markSideEffectsApplied
PERFORMANCE Shared/Random.swift:28 groupDefaults computed static var — UserDefaults(suiteName:) on every hot-path access
PERFORMANCE Shared/Random.swift:73 NumberFormatter created inside dayFormat() on every call
PERFORMANCE Shared/Views/DayView/DayView.swift:17 Dead @AppStorage deleteEnabled — unnecessary UserDefaults subscription on root list view
WARNING Shared/Views/MonthView/MonthView.swift:410 @ObservedObject demoManager singleton in Equatable struct — excluded from equality check
WARNING Shared/Services/HealthService.swift:440 365 concurrent group.addTask for HealthKit — floods cooperative thread pool

Codable/Networking Auditor (31 findings)

Severity File Finding
CRITICAL Shared/Color+Codable.swift:33 encode(to:) throws at runtime when cgColor is nil — semantic colors crash encode
CRITICAL Shared/IAPManager.swift:289 try? on subscription.status — StoreKit error silently grants premium access
CRITICAL Shared/Persisence/DataControllerGET.swift:74 Infinite loop in calculateStreak when date(byAdding:) returns nil
CRITICAL Shared/MoodStreakActivity.swift:215 Same infinite loop in LiveActivityScheduler.calculateStreak
CRITICAL Shared/Persisence/ExtensionDataProvider.swift:199 Non-atomic delete-save-insert — widget mood log can permanently delete entry
BUG Shared/Color+Codable.swift:84 Scanner.scanHexInt64 return value discarded — invalid hex silently produces black
BUG Shared/Color+Codable.swift:86 Non-6/8 char hex silently produces black — no validation
BUG Shared/Color+Codable.swift:137 RawRepresentable for Color drops alpha channel — @AppStorage loses transparency
BUG Shared/Onboarding/OnboardingData.swift:52 rawValue returns "[]" on encode failure — next decode produces nil, resets onboarding
BUG Shared/LocalNotification.swift:21 testIfEnabled completion never called when user denies without error — permanent hang
BUG Shared/Date+Extensions.swift:16 Failable init?(rawValue:) never returns nil — bad string produces Jan 1, 2001
BUG Shared/SharedMoodIntent.swift:109 try? delete-save + non-atomic insert — permanent data loss path
BUG Shared/BGTask.swift:42 Error interpolation typo — (error) literal always printed
BUG Shared/Services/WatchConnectivityManager.swift:117 pendingMoods data race — concurrent read/write without synchronization
BUG Shared/Services/ExportService.swift:688 Force-unwrap calendar.date(byAdding:) in PDF generation
BUG Shared/Models/MoodTintable.swift:89 Color.init(from:) calls init(hex:) which never throws — invalid hex decoded as black
WARNING Shared/Onboarding/OnboardingData.swift:41 try? in init?(rawValue:) silently resets reminder on schema change
WARNING Shared/Models/UserDefaultsStore.swift:227 try? decode OnboardingData silently returns defaults on corruption
WARNING Shared/Models/UserDefaultsStore.swift:315 try? decode [CustomWidgetModel] on failure calls removeObject — wipes all user widgets
WARNING Shared/Models/UserDefaultsStore.swift:324 try? encode widgets — unperisted widgets reappear on next launch
WARNING Shared/Models/UserDefaultsStore.swift:330 Double try? decode in fallback path
WARNING Shared/Models/UserDefaultsStore.swift:309 synchronize() deprecated no-op
WARNING Shared/Models/UserDefaultsStore.swift:251 print only error handling for JSONEncoder failures in saveOnboarding
WARNING Shared/Models/UserDefaultsStore.swift:365 print only error handling in saveCustomWidget
WARNING Shared/Models/UserDefaultsStore.swift:395 print only error handling in deleteCustomWidget — deleted widget reappears after crash
WARNING Shared/MoodLogger.swift:73 try? swallows HealthKit save error — sync silently fails without log entry
WARNING Shared/MoodStreakActivity.swift:139 try? in getUserRatingTime — nil blocks all Live Activity scheduling silently
WARNING Shared/LocalNotification.swift:35 try? in rescheduleNotifiations — decode fail silently stops all future notifications
WARNING Shared/Persisence/DataControllerGET.swift:24 try? on modelContext.fetch — database error indistinguishable from "no entry"
WARNING Shared/Persisence/DataControllerADD.swift:43 try? on fetch in fillInMissingDates — silent failure leaves history gaps permanently
WARNING Shared/Analytics.swift:552 Force-unwrap after nil check — antipattern, crash risk on future refactor
WARNING Shared/MoodLogger.swift:134 ISO8601DateFormatter() allocated per call — expensive on hot path
WARNING Shared/Services/ExportService.swift:49 ExportService formatters not thread-safe — concurrent export causes garbled dates

Cross-Cutting Deep Audit (20 findings)

Severity File Finding
CRITICAL Shared/Persisence/DataControllerGET.swift:74 Infinite loopwhile true + ?? checkDate never advances on nil Calendar result; confirmed in MoodStreakActivity.swift:215 and AppShortcuts.swift:128140
CRITICAL Shared/Persisence/DataControllerADD.swift:13 Data loss — non-atomic delete-save-insert; old entry deleted, new entry not persisted if second save fails; duplicated in ExtensionDataProvider.swift:199 and SharedMoodIntent.swift:109
CRITICAL Shared/SharedMoodIntent.swift:93 Store corruption — new ModelContainer per widget intent against live SQLite file; ExtensionDataProvider correctly caches; intent does not
CRITICAL Shared/BGTask.swift:42 Invisible failures(error) typo, all scheduling failures print literal text; @MainActor annotation runs Core Data on main thread; setTaskCompleted fires before async work completes
CRITICAL Shared/IAPManager.swift:271 Revenue leaktry? on subscription.status falls through to unconditional state = .subscribed on any StoreKit error
CRITICAL Shared/MoodStreakActivity.swift:38 Race conditionstartStreakActivity uses an unstructured Task; rapid concurrent calls start multiple Live Activities; orphaned activities persist on Lock Screen
CRITICAL Shared/Persisence/DataControllerADD.swift:46 Data integrity — future-dated entry causes fillInMissingDates to insert entries for every day from now to that future date
CRITICAL Shared/MoodLogger.swift:108 Logic errorprocessPendingSideEffects uses getData(includedDays: []) = "all days", bypassing user's day-filter setting; streak counter differs from filtered day view
CRITICAL Shared/Views/SettingsView/SettingsTabView.swift:61 Guaranteed crashCustomizeContentView missing iapManager/authManager environment objects
CRITICAL Shared/Onboarding/views/OnboardingSubscription.swift:139 Onboarding loop — completion closure fires on every sheet dismiss including cancel; onboardingCompleted event fires twice when user visits paywall then uses skip path
BUG Shared/Models/UserDefaultsStore.swift:217 Data race — static cachedOnboardingData accessed from main actor and BGTask without synchronization; wrong dates propagate to CloudKit
BUG Shared/Persisence/DataController.swift:51 Memory leak + stale callbackseditedDataClosure grows forever, stale closures from deallocated ViewModels called on every save; no removal mechanism
BUG Shared/Views/CelebrationAnimations.swift:109 Ghost entriesDispatchQueue.asyncAfter mood-save fires after view dismissal; duplicate entries possible on rapid navigation
BUG Shared/Persisence/DataControllerGET.swift:18 Off-by-oneentry.forDate <= endDate where endDate is midnight of next day; 8 identical predicates across codebase
BUG Shared/Services/WatchConnectivityManager.swift:117 Data racependingMoods read/written from WCSession delegate background thread and main thread without any synchronization
BUG Shared/Models/Color+Codable.swift:33 Silent encode crashencode(to:) throws when cgColor is nil for semantic colors; custom theme silently lost
BUG Shared/Date+Extensions.swift:16 Silent bad date — failable init?(rawValue:) never returns nil; bad string produces Jan 1, 2001, breaking trial expiration for first-launch users
BUG Shared/Models/MoodEntryFunctions.swift:25 Data display corruptionnewGrouped[year] = newMonth inside inner month loop overwrites all months; confirmed by source read; all months but last invisible for multi-year users
BUG Shared/Persisence/DataController.swift:55 Silent failuresaveAndRunDataListeners() runs listeners even when save() fails; UI shows unsaved state as if it were persisted
BUG CLAUDE.md:24 Doc mismatch — documentation says Shared/Persistence/ (correct spelling) but actual directory is Shared/Persisence/ (typo) and files named DataController*.swift not Persistence*.swift

Summary

Finding Counts by Auditor

Auditor Findings
Memory Auditor 15
IAP Auditor 16
Concurrency Auditor 33
SwiftUI Performance Auditor 33
Security Auditor 33
Accessibility Auditor 51
Energy/Battery Auditor 23
Storage Auditor 28
iCloud/CloudKit Auditor 22
SwiftUI Architecture Auditor 49
Swift Performance Auditor 31
Modernization Auditor 43
Navigation Auditor 27
Testing Auditor 47
Build Optimizer Auditor 38
Codable/Networking Auditor 33
Cross-Cutting Deep Audit 20
TOTAL 542

Finding Counts by Severity

Severity Count
CRITICAL ~85
BUG ~185
PERFORMANCE ~145
WARNING ~127

Files with Most Findings

File Approx Findings Primary Issues
Shared/Persisence/DataControllerGET.swift 8 Infinite loop, off-by-one predicate, epoch fetch
Shared/Views/EntryListView.swift 12 Accelerometer leak, accessibility, performance
Shared/Views/SettingsView/SettingsTabView.swift 7 Missing env objects (crash), deprecated APIs
Shared/Persisence/DataControllerADD.swift 8 Non-atomic delete-insert, data loss, future-dated entries
Shared/IAPManager.swift 9 try? grants premium, trial clock, revocation
Shared/Analytics.swift 6 Hardcoded API key, race conditions, concurrency
Shared/BGTask.swift 6 @MainActor annotation, typo, timing bugs
Shared/Views/MonthView/MonthView.swift 10 ForEach ID collision, singletons in @StateObject
Shared/MoodStreakActivity.swift 7 Infinite loop, timer safety, race condition
Shared/Models/MoodEntryFunctions.swift 3 Inner loop overwrites year — critical data display bug

TOP 10 PRIORITIES

These are the highest-impact issues to fix first, ordered by severity and blast radius:

🔴 P1 — Fix Immediately (Data Loss / Security / Crash)

1. Infinite Loop in Streak CalculationDataControllerGET.swift:74, MoodStreakActivity.swift:215, AppShortcuts.swift:128140

  • Three separate while true + ?? checkDate loops that never terminate if Calendar.date(byAdding:) returns nil
  • Fix: Replace while true with a bounded loop (for _ in 0..<3650) or use the batch-fetch + in-memory iteration pattern already implemented in getCurrentStreak(includedDays:) (which correctly iterates over pre-fetched array)

2. Non-Atomic Delete-Save-Insert PatternDataControllerADD.swift:13, ExtensionDataProvider.swift:204, SharedMoodIntent.swift:109

  • Delete old entry → try? save() → insert new entry → save. If second save fails, entry permanently gone
  • Fix: Use SwiftData's rollback capability — only call save() once after both the delete and insert; wrap in a do/catch and rollback on failure

3. Hardcoded PostHog API KeyAnalytics.swift:24

  • Production API key committed to git history permanently — rotate the key in PostHog immediately
  • Fix: Rotate key in PostHog dashboard NOW; load from xcconfig/Info.plist not committed to repo

4. CustomizeContentView Missing Environment ObjectsSettingsTabView.swift:61

  • Any user navigating to the Customize tab in Settings crashes the app with a fatal missing EnvironmentObject error
  • Fix: Add .environmentObject(iapManager).environmentObject(authManager) to CustomizeContentView() at line 62

5. try? on StoreKit Status Grants Premium on ErrorIAPManager.swift:289

  • Any StoreKit network error during subscription status check causes the fallback at line 333 to grant .subscribed state
  • Fix: Replace try? with do { let statuses = try await ... } catch { /* log, set state to unknown */ return false }

🟠 P2 — Fix Before Next Release (Significant Impact)

6. Month Calendar Shows Only Last Month Per YearMoodEntryFunctions.swift:25

  • newGrouped[year] = newMonth is inside the inner month loop — every iteration overwrites the year entry, leaving only the last month
  • Fix: Move newGrouped[year] = newMonth to after the inner for key in monthKeys loop closes

7. ForEach ID Collision Across Years in MonthViewMonthView.swift:176

  • id: \.element.month (integer 112) collides across years — January 2023 and January 2024 share ID 1, one is silently dropped
  • Fix: Use id: \.element (the full WatchTimelineView object) or a composite ID like "\(year)-\(month)"

8. BGTask Scheduling Failure Invisible + Wrong ThreadBGTask.swift:14, 42

  • (error) typo makes all failures invisible; @MainActor annotation runs expensive Core Data fill on main thread
  • Fix: Fix typo (\(error)); remove @MainActor, dispatch work to background actor

9. ModelContainer Created Per Widget IntentSharedMoodIntent.swift:93

  • Every widget tap creates a new SQLite connection against the live store — concurrent writers can corrupt WAL state
  • Fix: Add a _container: ModelContainer? lazy cache pattern (already present in ExtensionDataProvider)

10. Missing PrivacyInfo.xcprivacy Manifest

  • App Store requires PrivacyInfo.xcprivacy for all apps using UserDefaults, NSUserDefaults, and third-party SDKs (PostHog) since May 2024
  • Fix: Create PrivacyInfo.xcprivacy declaring NSPrivacyAccessedAPICategoryUserDefaults with reason code CA92.1 (or applicable), and PostHog's required API types